From 656f9a07588cc00704825a78de9649ca4a1552b8 Mon Sep 17 00:00:00 2001 From: Dongxiao Xu Date: Mon, 28 Nov 2011 14:32:40 +0800 Subject: Hob: A new implemetation (v2) This commit implements a new design for hob Some of the new features: - Friendly new designed GUI. Quick response to user actions. - Two step builds support package generation and image generation. - Support running GUI seprarately from bitbake server. - Recipe/package selection and deselection. - Accurate customization for image contents and size. - Progress bars showing the parsing and build status. - Load/save user configurations from/into templates. (Bitbake rev: 4dacd29f9c957d20f4583330b51e5420f9c3338d) Signed-off-by: Dongxiao Xu Signed-off-by: Shane Wang Signed-off-by: Liming An Signed-off-by: Fengxia Hua Designed-by: Belen Barros Pena Signed-off-by: Richard Purdie --- bitbake/lib/bb/build.py | 10 +- bitbake/lib/bb/runqueue.py | 12 +- bitbake/lib/bb/ui/crumbs/builddetailspage.py | 110 ++ bitbake/lib/bb/ui/crumbs/builder.py | 873 +++++++++++++++ bitbake/lib/bb/ui/crumbs/configurator.py | 346 ------ bitbake/lib/bb/ui/crumbs/hig.py | 589 ++++++++++- bitbake/lib/bb/ui/crumbs/hobcolor.py | 35 + bitbake/lib/bb/ui/crumbs/hobeventhandler.py | 586 +++++----- bitbake/lib/bb/ui/crumbs/hoblistmodel.py | 765 ++++++++++++++ bitbake/lib/bb/ui/crumbs/hobpages.py | 87 ++ bitbake/lib/bb/ui/crumbs/hobprefs.py | 335 ------ bitbake/lib/bb/ui/crumbs/hobwidget.py | 805 ++++++++++++++ bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py | 358 +++++++ bitbake/lib/bb/ui/crumbs/imagedetailspage.py | 294 ++++++ bitbake/lib/bb/ui/crumbs/layereditor.py | 153 --- bitbake/lib/bb/ui/crumbs/packageselectionpage.py | 226 ++++ bitbake/lib/bb/ui/crumbs/progress.py | 20 - bitbake/lib/bb/ui/crumbs/progressbar.py | 52 + bitbake/lib/bb/ui/crumbs/recipeselectionpage.py | 221 ++++ bitbake/lib/bb/ui/crumbs/runningbuild.py | 55 +- bitbake/lib/bb/ui/crumbs/tasklistmodel.py | 620 ----------- bitbake/lib/bb/ui/crumbs/template.py | 180 ++++ bitbake/lib/bb/ui/hob.py | 1115 +------------------- bitbake/lib/bb/ui/icons/images/images_display.png | Bin 0 -> 6898 bytes bitbake/lib/bb/ui/icons/images/images_hover.png | Bin 0 -> 7051 bytes bitbake/lib/bb/ui/icons/indicators/alert.png | Bin 0 -> 3954 bytes .../lib/bb/ui/icons/indicators/confirmation.png | Bin 0 -> 5789 bytes bitbake/lib/bb/ui/icons/indicators/denied.png | Bin 0 -> 3955 bytes bitbake/lib/bb/ui/icons/indicators/error.png | Bin 0 -> 6482 bytes bitbake/lib/bb/ui/icons/indicators/issues.png | Bin 0 -> 4549 bytes bitbake/lib/bb/ui/icons/indicators/refresh.png | Bin 0 -> 5250 bytes bitbake/lib/bb/ui/icons/indicators/tick.png | Bin 0 -> 4563 bytes bitbake/lib/bb/ui/icons/info/info_display.png | Bin 0 -> 4760 bytes bitbake/lib/bb/ui/icons/info/info_hover.png | Bin 0 -> 4847 bytes bitbake/lib/bb/ui/icons/layers/layers_display.png | Bin 0 -> 5326 bytes bitbake/lib/bb/ui/icons/layers/layers_hover.png | Bin 0 -> 5390 bytes .../lib/bb/ui/icons/packages/packages_display.png | Bin 0 -> 7188 bytes .../lib/bb/ui/icons/packages/packages_hover.png | Bin 0 -> 7308 bytes bitbake/lib/bb/ui/icons/recipe/recipe_display.png | Bin 0 -> 4873 bytes bitbake/lib/bb/ui/icons/recipe/recipe_hover.png | Bin 0 -> 5003 bytes .../lib/bb/ui/icons/settings/settings_display.png | Bin 0 -> 6076 bytes .../lib/bb/ui/icons/settings/settings_hover.png | Bin 0 -> 6269 bytes .../bb/ui/icons/templates/templates_display.png | Bin 0 -> 5651 bytes .../lib/bb/ui/icons/templates/templates_hover.png | Bin 0 -> 5791 bytes bitbake/lib/bb/ui/uihelper.py | 42 + 45 files changed, 5078 insertions(+), 2811 deletions(-) create mode 100755 bitbake/lib/bb/ui/crumbs/builddetailspage.py create mode 100755 bitbake/lib/bb/ui/crumbs/builder.py delete mode 100644 bitbake/lib/bb/ui/crumbs/configurator.py create mode 100644 bitbake/lib/bb/ui/crumbs/hobcolor.py create mode 100644 bitbake/lib/bb/ui/crumbs/hoblistmodel.py create mode 100755 bitbake/lib/bb/ui/crumbs/hobpages.py delete mode 100644 bitbake/lib/bb/ui/crumbs/hobprefs.py create mode 100644 bitbake/lib/bb/ui/crumbs/hobwidget.py create mode 100644 bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py create mode 100755 bitbake/lib/bb/ui/crumbs/imagedetailspage.py delete mode 100644 bitbake/lib/bb/ui/crumbs/layereditor.py create mode 100755 bitbake/lib/bb/ui/crumbs/packageselectionpage.py delete mode 100644 bitbake/lib/bb/ui/crumbs/progress.py create mode 100644 bitbake/lib/bb/ui/crumbs/progressbar.py create mode 100755 bitbake/lib/bb/ui/crumbs/recipeselectionpage.py delete mode 100644 bitbake/lib/bb/ui/crumbs/tasklistmodel.py create mode 100644 bitbake/lib/bb/ui/crumbs/template.py mode change 100644 => 100755 bitbake/lib/bb/ui/hob.py create mode 100644 bitbake/lib/bb/ui/icons/images/images_display.png create mode 100644 bitbake/lib/bb/ui/icons/images/images_hover.png create mode 100644 bitbake/lib/bb/ui/icons/indicators/alert.png create mode 100644 bitbake/lib/bb/ui/icons/indicators/confirmation.png create mode 100644 bitbake/lib/bb/ui/icons/indicators/denied.png create mode 100644 bitbake/lib/bb/ui/icons/indicators/error.png create mode 100644 bitbake/lib/bb/ui/icons/indicators/issues.png create mode 100644 bitbake/lib/bb/ui/icons/indicators/refresh.png create mode 100644 bitbake/lib/bb/ui/icons/indicators/tick.png create mode 100644 bitbake/lib/bb/ui/icons/info/info_display.png create mode 100644 bitbake/lib/bb/ui/icons/info/info_hover.png create mode 100644 bitbake/lib/bb/ui/icons/layers/layers_display.png create mode 100644 bitbake/lib/bb/ui/icons/layers/layers_hover.png create mode 100644 bitbake/lib/bb/ui/icons/packages/packages_display.png create mode 100644 bitbake/lib/bb/ui/icons/packages/packages_hover.png create mode 100644 bitbake/lib/bb/ui/icons/recipe/recipe_display.png create mode 100644 bitbake/lib/bb/ui/icons/recipe/recipe_hover.png create mode 100644 bitbake/lib/bb/ui/icons/settings/settings_display.png create mode 100644 bitbake/lib/bb/ui/icons/settings/settings_hover.png create mode 100644 bitbake/lib/bb/ui/icons/templates/templates_display.png create mode 100644 bitbake/lib/bb/ui/icons/templates/templates_hover.png diff --git a/bitbake/lib/bb/build.py b/bitbake/lib/bb/build.py index 1e041a2a3e..e85d7c4f3e 100644 --- a/bitbake/lib/bb/build.py +++ b/bitbake/lib/bb/build.py @@ -153,8 +153,6 @@ def exec_func(func, d, dirs = None): bb.utils.mkdirhier(adir) ispython = flags.get('python') - if flags.get('fakeroot') and not flags.get('task'): - bb.fatal("Function %s specifies fakeroot but isn't a task?!" % func) lockflag = flags.get('lockfiles') if lockflag: @@ -223,9 +221,9 @@ def exec_func_shell(function, d, runfile, cwd=None): with open(runfile, 'w') as script: script.write('#!/bin/sh -e\n') - if bb.msg.loggerDefaultVerbose: - script.write("set -x\n") data.emit_func(function, script, d) + + script.write("set -x\n") if cwd: script.write("cd %s\n" % cwd) script.write("%s\n" % function) @@ -233,6 +231,10 @@ def exec_func_shell(function, d, runfile, cwd=None): os.chmod(runfile, 0775) cmd = runfile + if d.getVarFlag(function, 'fakeroot'): + fakerootcmd = d.getVar('FAKEROOT', True) + if fakerootcmd: + cmd = [fakerootcmd, runfile] if bb.msg.loggerDefaultVerbose: logfile = LogTee(logger, sys.stdout) diff --git a/bitbake/lib/bb/runqueue.py b/bitbake/lib/bb/runqueue.py index 3ad0c0aa92..1959007bec 100644 --- a/bitbake/lib/bb/runqueue.py +++ b/bitbake/lib/bb/runqueue.py @@ -1173,8 +1173,7 @@ class RunQueueExecute: logger.critical(str(exc)) os._exit(1) try: - if not self.cooker.configuration.dry_run: - ret = bb.build.exec_task(fn, taskname, the_data) + ret = bb.build.exec_task(fn, taskname, the_data) os._exit(ret) except: os._exit(1) @@ -1357,6 +1356,12 @@ class RunQueueExecuteTasks(RunQueueExecute): self.rqdata.get_user_idstring(task)) self.task_skip(task) return True + elif self.cooker.configuration.dry_run: + self.runq_running[task] = 1 + self.runq_buildable[task] = 1 + self.stats.taskActive() + self.task_complete(task) + return True taskdep = self.rqdata.dataCache.task_deps[fn] if 'noexec' in taskdep and taskname in taskdep['noexec']: @@ -1648,9 +1653,6 @@ class RunQueueExecuteScenequeue(RunQueueExecute): self.task_skip(task) return True - logger.info("Running setscene task %d of %d (%s:%s)" % (self.stats.completed + self.stats.active + self.stats.failed + 1, - self.stats.total, fn, taskname)) - startevent = sceneQueueTaskStarted(task, self.stats, self.rq) bb.event.fire(startevent, self.cfgData) diff --git a/bitbake/lib/bb/ui/crumbs/builddetailspage.py b/bitbake/lib/bb/ui/crumbs/builddetailspage.py new file mode 100755 index 0000000000..941f1e30b3 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/builddetailspage.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +from bb.ui.crumbs.progressbar import HobProgressBar +from bb.ui.crumbs.hobwidget import hic +from bb.ui.crumbs.runningbuild import RunningBuildTreeView +from bb.ui.crumbs.hobpages import HobPage + +# +# BuildDetailsPage +# + +class BuildDetailsPage (HobPage): + + def __init__(self, builder): + super(BuildDetailsPage, self).__init__(builder, "Building ...") + + # create visual elements + self.create_visual_elements() + + def create_visual_elements(self): + # create visual elements + self.vbox = gtk.VBox(False, 15) + + self.progress_box = gtk.HBox(False, 5) + self.progress_bar = HobProgressBar() + self.progress_box.pack_start(self.progress_bar, expand=True, fill=True) + self.stop_button = gtk.LinkButton("Stop the build process", "Stop") + self.stop_button.connect("clicked", self.stop_button_clicked_cb) + self.progress_box.pack_end(self.stop_button, expand=False, fill=False) + + self.build_tv = RunningBuildTreeView(readonly=True) + self.build_tv.set_model(self.builder.handler.build.model) + self.scrolled_view = gtk.ScrolledWindow () + self.scrolled_view.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.scrolled_view.add(self.build_tv) + + self.button_box = gtk.HBox(False, 5) + self.back_button = gtk.LinkButton("Go back to Image Configuration screen", "<< Back to image configuration") + self.back_button.connect("clicked", self.back_button_clicked_cb) + self.button_box.pack_start(self.back_button, expand=False, fill=False) + + def _remove_all_widget(self): + children = self.vbox.get_children() or [] + for child in children: + self.vbox.remove(child) + children = self.box_group_area.get_children() or [] + for child in children: + self.box_group_area.remove(child) + children = self.get_children() or [] + for child in children: + self.remove(child) + + def show_page(self, step): + self._remove_all_widget() + if step == self.builder.PACKAGE_GENERATING or step == self.builder.FAST_IMAGE_GENERATING: + self.title = "Building packages ..." + else: + self.title = "Building image ..." + self.build_details_top = self.add_onto_top_bar(None) + self.pack_start(self.build_details_top, expand=False, fill=False) + self.pack_start(self.group_align, expand=True, fill=True) + + self.box_group_area.pack_start(self.vbox, expand=True, fill=True) + + self.progress_bar.reset() + self.vbox.pack_start(self.progress_box, expand=False, fill=False) + + self.vbox.pack_start(self.scrolled_view, expand=True, fill=True) + + self.box_group_area.pack_end(self.button_box, expand=False, fill=False) + self.show_all() + self.back_button.hide() + + def update_progress_bar(self, title, fraction, status=True): + self.progress_bar.update(fraction) + self.progress_bar.set_title(title) + self.progress_bar.set_rcstyle(status) + + def back_button_clicked_cb(self, button): + self.builder.show_configuration() + + def show_back_button(self): + self.back_button.show() + + def stop_button_clicked_cb(self, button): + self.builder.stop_build() + + def hide_stop_button(self): + self.stop_button.hide() diff --git a/bitbake/lib/bb/ui/crumbs/builder.py b/bitbake/lib/bb/ui/crumbs/builder.py new file mode 100755 index 0000000000..007167337f --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/builder.py @@ -0,0 +1,873 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import copy +import os +import subprocess +import shlex +from bb.ui.crumbs.template import TemplateMgr +from bb.ui.crumbs.imageconfigurationpage import ImageConfigurationPage +from bb.ui.crumbs.recipeselectionpage import RecipeSelectionPage +from bb.ui.crumbs.packageselectionpage import PackageSelectionPage +from bb.ui.crumbs.builddetailspage import BuildDetailsPage +from bb.ui.crumbs.imagedetailspage import ImageDetailsPage +from bb.ui.crumbs.hobwidget import hwc +from bb.ui.crumbs.hig import CrumbsDialog, BinbDialog, \ + AdvancedSettingDialog, LayerSelectionDialog, \ + DeployImageDialog, ImageSelectionDialog + +class Configuration: + '''Represents the data structure of configuration.''' + + def __init__(self, params): + # Settings + self.curr_mach = "" + self.curr_distro = params["distro"] + self.dldir = params["dldir"] + self.sstatedir = params["sstatedir"] + self.sstatemirror = params["sstatemirror"] + self.pmake = params["pmake"] + self.bbthread = params["bbthread"] + self.curr_package_format = " ".join(params["pclass"].split("package_")).strip() + self.image_rootfs_size = params["image_rootfs_size"] + self.image_extra_size = params["image_extra_size"] + self.image_overhead_factor = params['image_overhead_factor'] + self.incompat_license = params["incompat_license"] + self.curr_sdk_machine = params["sdk_machine"] + self.extra_setting = {} + self.toolchain_build = False + self.image_fstypes = params["image_fstypes"].split() + # bblayers.conf + self.layers = params["layer"].split() + # image/recipes/packages + self.selected_image = None + self.selected_recipes = [] + self.selected_packages = [] + + def load(self, template): + self.curr_mach = template.getVar("MACHINE") + self.curr_package_format = " ".join(template.getVar("PACKAGE_CLASSES").split("package_")).strip() + self.curr_distro = template.getVar("DISTRO") + self.dldir = template.getVar("DL_DIR") + self.sstatedir = template.getVar("SSTATE_DIR") + self.sstatemirror = template.getVar("SSTATE_MIRROR") + self.pmake = int(template.getVar("PARALLEL_MAKE").split()[1]) + self.bbthread = int(template.getVar("BB_NUMBER_THREAD")) + self.image_rootfs_size = int(template.getVar("IMAGE_ROOTFS_SIZE")) + self.image_extra_size = int(template.getVar("IMAGE_EXTRA_SPACE")) + # image_overhead_factor is read-only. + self.incompat_license = template.getVar("INCOMPATIBLE_LICENSE") + self.curr_sdk_machine = template.getVar("SDKMACHINE") + self.extra_setting = eval(template.getVar("EXTRA_SETTING")) + self.toolchain_build = eval(template.getVar("TOOLCHAIN_BUILD")) + self.image_fstypes = template.getVar("IMAGE_FSTYPES").split() + # bblayers.conf + self.layers = template.getVar("BBLAYERS").split() + # image/recipes/packages + self.selected_image = template.getVar("__SELECTED_IMAGE__") + self.selected_recipes = template.getVar("DEPENDS").split() + self.selected_packages = template.getVar("IMAGE_INSTALL").split() + + def save(self, template, filename): + # bblayers.conf + template.setVar("BBLAYERS", " ".join(self.layers)) + # local.conf + template.setVar("MACHINE", self.curr_mach) + template.setVar("DISTRO", self.curr_distro) + template.setVar("DL_DIR", self.dldir) + template.setVar("SSTATE_DIR", self.sstatedir) + template.setVar("SSTATE_MIRROR", self.sstatemirror) + template.setVar("PARALLEL_MAKE", "-j %s" % self.pmake) + template.setVar("BB_NUMBER_THREAD", self.bbthread) + template.setVar("PACKAGE_CLASSES", " ".join(["package_" + i for i in self.curr_package_format.split()])) + template.setVar("IMAGE_ROOTFS_SIZE", self.image_rootfs_size) + template.setVar("IMAGE_EXTRA_SPACE", self.image_extra_size) + template.setVar("INCOMPATIBLE_LICENSE", self.incompat_license) + template.setVar("SDKMACHINE", self.curr_sdk_machine) + template.setVar("EXTRA_SETTING", self.extra_setting) + template.setVar("TOOLCHAIN_BUILD", self.toolchain_build) + template.setVar("IMAGE_FSTYPES", " ".join(self.image_fstypes).lstrip(" ")) + # image/recipes/packages + self.selected_image = filename + template.setVar("__SELECTED_IMAGE__", self.selected_image) + template.setVar("DEPENDS", self.selected_recipes) + template.setVar("IMAGE_INSTALL", self.selected_packages) + +class Parameters: + '''Represents other variables like available machines, etc.''' + + def __init__(self, params): + # Variables + self.all_machines = [] + self.all_package_formats = [] + self.all_distros = [] + self.all_sdk_machines = [] + self.max_threads = params["max_threads"] + self.all_layers = [] + self.core_base = params["core_base"] + self.image_names = [] + self.image_addr = params["image_addr"] + self.image_types = params["image_types"].split() + +class Builder(gtk.Window): + + (MACHINE_SELECTION, + LAYER_CHANGED, + RCPPKGINFO_POPULATING, + RCPPKGINFO_POPULATED, + RECIPE_SELECTION, + PACKAGE_GENERATING, + PACKAGE_GENERATED, + PACKAGE_SELECTION, + FAST_IMAGE_GENERATING, + IMAGE_GENERATING, + IMAGE_GENERATED, + MY_IMAGE_OPENED, + BACK, + END_NOOP) = range(14) + + (IMAGE_CONFIGURATION, + RECIPE_DETAILS, + BUILD_DETAILS, + PACKAGE_DETAILS, + IMAGE_DETAILS, + END_TAB) = range(6) + + __step2page__ = { + MACHINE_SELECTION : IMAGE_CONFIGURATION, + LAYER_CHANGED : IMAGE_CONFIGURATION, + RCPPKGINFO_POPULATING : IMAGE_CONFIGURATION, + RCPPKGINFO_POPULATED : IMAGE_CONFIGURATION, + RECIPE_SELECTION : RECIPE_DETAILS, + PACKAGE_GENERATING : BUILD_DETAILS, + PACKAGE_GENERATED : PACKAGE_DETAILS, + PACKAGE_SELECTION : PACKAGE_DETAILS, + FAST_IMAGE_GENERATING : BUILD_DETAILS, + IMAGE_GENERATING : BUILD_DETAILS, + IMAGE_GENERATED : IMAGE_DETAILS, + MY_IMAGE_OPENED : IMAGE_DETAILS, + END_NOOP : None, + } + + def __init__(self, hobHandler, recipe_model, package_model): + super(Builder, self).__init__() + + # handler + self.handler = hobHandler + + self.template = None + + # settings + params = self.handler.get_parameters() + self.configuration = Configuration(params) + self.parameters = Parameters(params) + + # build step + self.current_step = None + self.previous_step = None + + self.stopping = False + self.build_succeeded = True + + # recipe model and package model + self.recipe_model = recipe_model + self.package_model = package_model + + # create visual elements + self.create_visual_elements() + + # connect the signals to functions + #self.connect("configure-event", self.resize_window_cb) + self.connect("delete-event", self.destroy_window_cb) + self.recipe_model.connect ("recipe-selection-changed", self.recipelist_changed_cb) + self.package_model.connect("package-selection-changed", self.packagelist_changed_cb) + self.recipe_model.connect ("recipelist-populated", self.recipelist_populated_cb) + self.package_model.connect("packagelist-populated", self.packagelist_populated_cb) + self.handler.connect("config-updated", self.handler_config_updated_cb) + self.handler.connect("package-formats-updated", self.handler_package_formats_updated_cb) + self.handler.connect("layers-updated", self.handler_layers_updated_cb) + self.handler.connect("parsing-started", self.handler_parsing_started_cb) + self.handler.connect("parsing", self.handler_parsing_cb) + self.handler.connect("parsing-completed", self.handler_parsing_completed_cb) + self.handler.build.connect("build-started", self.handler_build_started_cb) + self.handler.build.connect("build-succeeded", self.handler_build_succeeded_cb) + self.handler.build.connect("build-failed", self.handler_build_failed_cb) + self.handler.build.connect("task-started", self.handler_task_started_cb) + self.handler.connect("generating-data", self.handler_generating_data_cb) + self.handler.connect("data-generated", self.handler_data_generated_cb) + self.handler.connect("command-succeeded", self.handler_command_succeeded_cb) + self.handler.connect("command-failed", self.handler_command_failed_cb) + + self.switch_page(self.MACHINE_SELECTION) + + def create_visual_elements(self): + self.set_title("HOB -- Image Creator") + self.set_icon_name("applications-development") + self.set_position(gtk.WIN_POS_CENTER_ALWAYS) + self.set_resizable(True) + window_width = self.get_screen().get_width() + window_height = self.get_screen().get_height() + if window_width >= hwc.MAIN_WIN_WIDTH: + window_width = hwc.MAIN_WIN_WIDTH + window_height = hwc.MAIN_WIN_HEIGHT + else: + lbl = "Screen dimension mismatched\nfor better usability and visual effects," + lbl = lbl + " the screen dimension should be 1024x768 or above." + dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) + dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + dialog.run() + dialog.destroy() + self.set_size_request(window_width, window_height) + + self.vbox = gtk.VBox(False, 0) + self.vbox.set_border_width(0) + self.add(self.vbox) + + # create pages + self.image_configuration_page = ImageConfigurationPage(self) + self.recipe_details_page = RecipeSelectionPage(self) + self.build_details_page = BuildDetailsPage(self) + self.package_details_page = PackageSelectionPage(self) + self.image_details_page = ImageDetailsPage(self) + + self.nb = gtk.Notebook() + self.nb.set_show_tabs(False) + self.nb.insert_page(self.image_configuration_page, None, self.IMAGE_CONFIGURATION) + self.nb.insert_page(self.recipe_details_page, None, self.RECIPE_DETAILS) + self.nb.insert_page(self.build_details_page, None, self.BUILD_DETAILS) + self.nb.insert_page(self.package_details_page, None, self.PACKAGE_DETAILS) + self.nb.insert_page(self.image_details_page, None, self.IMAGE_DETAILS) + self.vbox.pack_start(self.nb, expand=True, fill=True) + + self.show_all() + self.nb.set_current_page(0) + + def get_split_model(self): + return self.handler.split_model + + def load_template(self, path): + self.template = TemplateMgr() + self.template.load(path) + self.configuration.load(self.template) + + if self.get_split_model(): + if not set(self.configuration.layers) <= set(self.parameters.all_layers): + return False + else: + for layer in self.configuration.layers: + if not os.path.exists(layer+'/conf/layer.conf'): + return False + + self.switch_page(self.LAYER_CHANGED) + + self.template.destroy() + self.template = None + + def save_template(self, path): + if path.rfind("/") == -1: + filename = "default" + path = "." + else: + filename = path[path.rfind("/") + 1:len(path)] + path = path[0:path.rfind("/")] + + self.template = TemplateMgr() + self.template.open(filename, path) + self.configuration.save(self.template, filename) + + self.template.save() + self.template.destroy() + self.template = None + + def switch_page(self, next_step): + # Main Workflow (Business Logic) + self.nb.set_current_page(self.__step2page__[next_step]) + + if next_step == self.MACHINE_SELECTION: # init step + self.image_configuration_page.show_machine() + + elif next_step == self.LAYER_CHANGED: + # after layers is changd by users + self.image_configuration_page.show_machine() + self.handler.refresh_layers(self.configuration.layers) + + elif next_step == self.RCPPKGINFO_POPULATING: + # MACHINE CHANGED action or SETTINGS CHANGED + # show the progress bar + self.image_configuration_page.show_info_populating() + self.generate_recipes() + + elif next_step == self.RCPPKGINFO_POPULATED: + self.image_configuration_page.show_info_populated() + + elif next_step == self.RECIPE_SELECTION: + pass + + elif next_step == self.PACKAGE_SELECTION: + pass + + elif next_step == self.PACKAGE_GENERATING or next_step == self.FAST_IMAGE_GENERATING: + # both PACKAGE_GENEATING and FAST_IMAGE_GENERATING share the same page + self.build_details_page.show_page(next_step) + self.generate_packages() + + elif next_step == self.PACKAGE_GENERATED: + pass + + elif next_step == self.IMAGE_GENERATING: + # after packages are generated, selected_packages need to + # be updated in package_model per selected_image in recipe_model + self.build_details_page.show_page(next_step) + self.generate_image() + + elif next_step == self.IMAGE_GENERATED: + self.image_details_page.show_page(next_step) + + elif next_step == self.MY_IMAGE_OPENED: + self.image_details_page.show_page(next_step) + + self.previous_step = self.current_step + self.current_step = next_step + + def set_user_config(self): + self.handler.init_cooker() + # set bb layers + self.handler.set_bblayers(self.configuration.layers) + # set local configuration + self.handler.set_machine(self.configuration.curr_mach) + self.handler.set_package_format(self.configuration.curr_package_format) + self.handler.set_distro(self.configuration.curr_distro) + self.handler.set_dl_dir(self.configuration.dldir) + self.handler.set_sstate_dir(self.configuration.sstatedir) + self.handler.set_sstate_mirror(self.configuration.sstatemirror) + self.handler.set_pmake(self.configuration.pmake) + self.handler.set_bbthreads(self.configuration.bbthread) + self.handler.set_rootfs_size(self.configuration.image_rootfs_size) + self.handler.set_extra_size(self.configuration.image_extra_size) + self.handler.set_incompatible_license(self.configuration.incompat_license) + self.handler.set_sdk_machine(self.configuration.curr_sdk_machine) + self.handler.set_image_fstypes(self.configuration.image_fstypes) + self.handler.set_extra_config(self.configuration.extra_setting) + self.handler.set_extra_inherit("packageinfo") + + def reset_recipe_model(self): + self.recipe_model.reset() + + def reset_package_model(self): + self.package_model.reset() + + def update_recipe_model(self, selected_image, selected_recipes): + self.recipe_model.set_selected_image(selected_image) + self.recipe_model.set_selected_recipes(selected_recipes) + + def update_package_model(self, selected_packages): + left = self.package_model.set_selected_packages(selected_packages) + self.configuration.selected_packages += left + + def generate_packages(self): + # Build packages + _, all_recipes = self.recipe_model.get_selected_recipes() + self.set_user_config() + self.handler.reset_build() + self.handler.generate_packages(all_recipes) + + def generate_recipes(self): + # Parse recipes + self.set_user_config() + self.handler.generate_recipes() + + def generate_image(self): + # Build image + self.set_user_config() + all_packages = self.package_model.get_selected_packages() + self.handler.reset_build() + self.handler.generate_image(all_packages, self.configuration.toolchain_build) + + + # Callback Functions + def handler_config_updated_cb(self, handler, which, values): + if which == "distro": + self.parameters.all_distros = values + elif which == "machine": + self.parameters.all_machines = values + self.image_configuration_page.update_machine_combo() + elif which == "machine-sdk": + self.parameters.all_sdk_machines = values + + def handler_package_formats_updated_cb(self, handler, formats): + self.parameters.all_package_formats = formats + + def handler_layers_updated_cb(self, handler, layers): + self.parameters.all_layers = layers + + def handler_command_succeeded_cb(self, handler, initcmd): + if initcmd == self.handler.LAYERS_REFRESH: + self.image_configuration_page.switch_machine_combo() + elif initcmd in [self.handler.GENERATE_RECIPES, + self.handler.GENERATE_PACKAGES, + self.handler.GENERATE_IMAGE]: + self.handler.request_package_info_async() + elif initcmd == self.handler.POPULATE_PACKAGEINFO: + if self.current_step == self.FAST_IMAGE_GENERATING: + self.switch_page(self.IMAGE_GENERATING) + elif self.current_step == self.RCPPKGINFO_POPULATING: + self.switch_page(self.RCPPKGINFO_POPULATED) + elif self.current_step == self.PACKAGE_GENERATING: + self.switch_page(self.PACKAGE_GENERATED) + elif self.current_step == self.IMAGE_GENERATING: + self.switch_page(self.IMAGE_GENERATED) + + def handler_command_failed_cb(self, handler, msg): + lbl = "Error\n" + lbl = lbl + "%s\n\n" % msg + dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) + dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + response = dialog.run() + dialog.destroy() + self.handler.clear_busy() + self.configuration.curr_mach = None + self.image_configuration_page.switch_machine_combo() + self.switch_page(self.MACHINE_SELECTION) + + def window_sensitive(self, sensitive): + self.set_sensitive(sensitive) + if sensitive: + self.get_root_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) + else: + self.get_root_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + + + def handler_generating_data_cb(self, handler): + self.window_sensitive(False) + + def handler_data_generated_cb(self, handler): + self.window_sensitive(True) + + def recipelist_populated_cb(self, recipe_model): + selected_image = self.configuration.selected_image + selected_recipes = self.configuration.selected_recipes[:] + selected_packages = self.configuration.selected_packages[:] + + self.recipe_model.image_list_append(selected_image, + " ".join(selected_recipes), + " ".join(selected_packages)) + + self.image_configuration_page.update_image_combo(self.recipe_model, selected_image) + + self.update_recipe_model(selected_image, selected_recipes) + + def packagelist_populated_cb(self, package_model): + selected_packages = self.configuration.selected_packages[:] + self.update_package_model(selected_packages) + + def recipelist_changed_cb(self, recipe_model): + self.recipe_details_page.refresh_selection() + + def packagelist_changed_cb(self, package_model): + self.package_details_page.refresh_selection() + + def handler_parsing_started_cb(self, handler, message): + if self.current_step != self.RCPPKGINFO_POPULATING: + return + + fraction = 0 + if message["eventname"] == "TreeDataPreparationStarted": + fraction = 0.6 + fraction + self.image_configuration_page.update_progress_bar(message["title"], fraction) + + def handler_parsing_cb(self, handler, message): + if self.current_step != self.RCPPKGINFO_POPULATING: + return + + fraction = message["current"] * 1.0/message["total"] + if message["eventname"] == "TreeDataPreparationProgress": + fraction = 0.6 + 0.4 * fraction + else: + fraction = 0.6 * fraction + self.image_configuration_page.update_progress_bar(message["title"], fraction) + + def handler_parsing_completed_cb(self, handler, message): + if self.current_step != self.RCPPKGINFO_POPULATING: + return + + if message["eventname"] == "TreeDataPreparationCompleted": + fraction = 1.0 + else: + fraction = 0.6 + self.image_configuration_page.update_progress_bar(message["title"], fraction) + + def handler_build_started_cb(self, running_build): + if self.current_step == self.FAST_IMAGE_GENERATING: + fraction = 0 + elif self.current_step == self.IMAGE_GENERATING: + if self.previous_step == self.FAST_IMAGE_GENERATING: + fraction = 0.9 + else: + fraction = 0 + elif self.current_step == self.PACKAGE_GENERATING: + fraction = 0 + self.build_details_page.update_progress_bar("Build Started: ", fraction) + + def handler_build_succeeded_cb(self, running_build): + self.build_succeeded = True + if self.current_step == self.FAST_IMAGE_GENERATING: + fraction = 0.9 + elif self.current_step == self.IMAGE_GENERATING: + fraction = 1.0 + self.parameters.image_names = [] + linkname = 'hob-image-' + self.configuration.curr_mach + for image_type in self.parameters.image_types: + linkpath = self.parameters.image_addr + '/' + linkname + '.' + image_type + if os.path.exists(linkpath): + self.parameters.image_names.append(os.readlink(linkpath)) + elif self.current_step == self.PACKAGE_GENERATING: + fraction = 1.0 + self.build_details_page.update_progress_bar("Build Completed: ", fraction) + + def handler_build_failed_cb(self, running_build): + self.build_succeeded = False + if self.current_step == self.FAST_IMAGE_GENERATING: + fraction = 0.9 + elif self.current_step == self.IMAGE_GENERATING: + fraction = 1.0 + elif self.current_step == self.PACKAGE_GENERATING: + fraction = 1.0 + self.build_details_page.update_progress_bar("Build Failed: ", fraction, False) + self.build_details_page.show_back_button() + self.build_details_page.hide_stop_button() + self.handler.build_failed_async() + self.stopping = False + + def handler_task_started_cb(self, running_build, message): + fraction = message["current"] * 1.0/message["total"] + title = "Build packages" + if self.current_step == self.FAST_IMAGE_GENERATING: + if message["eventname"] == "sceneQueueTaskStarted": + fraction = 0.27 * fraction + elif message["eventname"] == "runQueueTaskStarted": + fraction = 0.27 + 0.63 * fraction + elif self.current_step == self.IMAGE_GENERATING: + title = "Build image" + if self.previous_step == self.FAST_IMAGE_GENERATING: + if message["eventname"] == "sceneQueueTaskStarted": + fraction = 0.27 + 0.63 + 0.03 * fraction + elif message["eventname"] == "runQueueTaskStarted": + fraction = 0.27 + 0.63 + 0.03 + 0.07 * fraction + else: + if message["eventname"] == "sceneQueueTaskStarted": + fraction = 0.2 * fraction + elif message["eventname"] == "runQueueTaskStarted": + fraction = 0.2 + 0.8 * fraction + elif self.current_step == self.PACKAGE_GENERATING: + if message["eventname"] == "sceneQueueTaskStarted": + fraction = 0.2 * fraction + elif message["eventname"] == "runQueueTaskStarted": + fraction = 0.2 + 0.8 * fraction + self.build_details_page.update_progress_bar(title + ": ", fraction) + + def destroy_window_cb(self, widget, event): + lbl = "Do you really want to exit the Hob image creator?" + dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) + dialog.add_button(gtk.STOCK_YES, gtk.RESPONSE_YES) + dialog.add_button(gtk.STOCK_NO, gtk.RESPONSE_NO) + dialog.set_default_response(gtk.RESPONSE_NO) + response = dialog.run() + dialog.destroy() + if response == gtk.RESPONSE_YES: + gtk.main_quit() + return False + else: + return True + + def build_packages(self): + _, all_recipes = self.recipe_model.get_selected_recipes() + if not all_recipes: + lbl = "No selections made\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 + self.switch_page(self.PACKAGE_GENERATING) + + def build_image(self): + selected_packages = self.package_model.get_selected_packages() + if not selected_packages: + lbl = "No selections made\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 + self.switch_page(self.IMAGE_GENERATING) + + def just_bake(self): + selected_image = self.recipe_model.get_selected_image() + selected_packages = self.package_model.get_selected_packages() or [] + + # If no base image and no selected packages don't build anything + if not (selected_packages or selected_image != self.recipe_model.__dummy_image__): + lbl = "No selections made\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 + + self.switch_page(self.FAST_IMAGE_GENERATING) + + def show_binb_dialog(self, binb): + binb_dialog = BinbDialog("Brought in by:", binb, self) + binb_dialog.run() + binb_dialog.destroy() + + def show_layer_selection_dialog(self): + dialog = LayerSelectionDialog(title = "Layer Selection", + layers = copy.deepcopy(self.configuration.layers), + all_layers = self.parameters.all_layers, + split_model = self.get_split_model(), + parent = self, + flags = gtk.DIALOG_MODAL + | gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR, + buttons = (gtk.STOCK_OK, gtk.RESPONSE_YES, + gtk.STOCK_CANCEL, gtk.RESPONSE_NO)) + response = dialog.run() + if response == gtk.RESPONSE_YES: + self.configuration.layers = dialog.layers + # DO refresh layers + if dialog.layers_changed: + self.switch_page(self.LAYER_CHANGED) + dialog.destroy() + + def show_load_template_dialog(self): + dialog = gtk.FileChooserDialog("Load Template Files", self, + gtk.FILE_CHOOSER_ACTION_SAVE, + (gtk.STOCK_OPEN, gtk.RESPONSE_YES, + gtk.STOCK_CANCEL, gtk.RESPONSE_NO)) + filter = gtk.FileFilter() + filter.set_name("HOB Files") + filter.add_pattern("*.hob") + dialog.add_filter(filter) + + response = dialog.run() + if response == gtk.RESPONSE_YES: + path = dialog.get_filename() + self.load_template(path) + dialog.destroy() + + def show_save_template_dialog(self): + dialog = gtk.FileChooserDialog("Save Template Files", self, + gtk.FILE_CHOOSER_ACTION_SAVE, + (gtk.STOCK_SAVE, gtk.RESPONSE_YES, + gtk.STOCK_CANCEL, gtk.RESPONSE_NO)) + dialog.set_current_name("hob") + response = dialog.run() + if response == gtk.RESPONSE_YES: + path = dialog.get_filename() + self.save_template(path) + dialog.destroy() + + def show_load_my_images_dialog(self): + dialog = ImageSelectionDialog(self.parameters.image_addr, self.parameters.image_types, + "Open My Images", self, + gtk.FILE_CHOOSER_ACTION_SAVE, + (gtk.STOCK_OPEN, gtk.RESPONSE_YES, + gtk.STOCK_CANCEL, gtk.RESPONSE_NO)) + + response = dialog.run() + if response == gtk.RESPONSE_YES: + if not dialog.image_names: + lbl = "No selections made\nYou have not made any selections" + crumbs_dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) + crumbs_dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + crumbs_dialog.run() + crumbs_dialog.destroy() + dialog.destroy() + return + + self.parameters.image_addr = dialog.image_folder + self.parameters.image_names = dialog.image_names[:] + self.switch_page(self.MY_IMAGE_OPENED) + + dialog.destroy() + + def show_adv_settings_dialog(self): + dialog = AdvancedSettingDialog(title = "Settings", + configuration = copy.deepcopy(self.configuration), + all_image_types = self.parameters.image_types, + all_package_formats = self.parameters.all_package_formats, + all_distros = self.parameters.all_distros, + all_sdk_machines = self.parameters.all_sdk_machines, + max_threads = self.parameters.max_threads, + split_model = self.get_split_model(), + parent = self, + flags = gtk.DIALOG_MODAL + | gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR, + buttons = ("Save", gtk.RESPONSE_YES, + gtk.STOCK_CANCEL, gtk.RESPONSE_NO)) + response = dialog.run() + if response == gtk.RESPONSE_YES: + self.configuration = dialog.configuration + # DO reparse recipes + if dialog.settings_changed: + if self.configuration.curr_mach == "": + self.switch_page(self.MACHINE_SELECTION) + else: + self.switch_page(self.RCPPKGINFO_POPULATING) + dialog.destroy() + + def deploy_image(self, image_name): + if not image_name: + lbl = "Please select an image to deploy." + dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) + dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + dialog.run() + dialog.destroy() + return + + image_path = os.path.join(self.parameters.image_addr, image_name) + dialog = DeployImageDialog(title = "Usb Image Maker", + image_path = image_path, + parent = self, + flags = gtk.DIALOG_MODAL + | gtk.DIALOG_DESTROY_WITH_PARENT + | gtk.DIALOG_NO_SEPARATOR, + buttons = ("Close", gtk.RESPONSE_NO, + "Make usb image", gtk.RESPONSE_YES)) + response = dialog.run() + dialog.destroy() + + def runqemu_image(self, image_name): + if not image_name: + lbl = "Please select an image to launch in QEMU." + dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) + dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + dialog.run() + dialog.destroy() + return + + dialog = gtk.FileChooserDialog("Load Kernel Files", self, + gtk.FILE_CHOOSER_ACTION_SAVE, + (gtk.STOCK_OPEN, gtk.RESPONSE_YES, + gtk.STOCK_CANCEL, gtk.RESPONSE_NO)) + filter = gtk.FileFilter() + filter.set_name("Kernel Files") + filter.add_pattern("*.bin") + dialog.add_filter(filter) + + dialog.set_current_folder(self.parameters.image_addr) + + response = dialog.run() + if response == gtk.RESPONSE_YES: + kernel_path = dialog.get_filename() + image_path = os.path.join(self.parameters.image_addr, image_name) + dialog.destroy() + + if response == gtk.RESPONSE_YES: + source_env_path = os.path.join(self.parameters.core_base, "oe-init-build-env") + tmp_path = os.path.join(os.getcwd(), "tmp") + if os.path.exists(image_path) and os.path.exists(kernel_path) \ + and os.path.exists(source_env_path) and os.path.exists(tmp_path): + cmdline = "/usr/bin/xterm -e " + cmdline += "\" export OE_TMPDIR=" + tmp_path + "; " + cmdline += "source " + source_env_path + " " + os.getcwd() + "; " + cmdline += "runqemu " + kernel_path + " " + image_path + "; bash\"" + subprocess.Popen(shlex.split(cmdline)) + else: + lbl = "Path error\nOne of your paths is wrong," + lbl = lbl + " please make sure the following paths exist:\n" + lbl = lbl + "image path:" + image_path + "\n" + lbl = lbl + "kernel path:" + kernel_path + "\n" + lbl = lbl + "source environment path:" + source_env_path + "\n" + lbl = lbl + "tmp path: " + tmp_path + "." + dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) + dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + dialog.run() + dialog.destroy() + + def show_packages(self, ask=True): + _, selected_recipes = self.recipe_model.get_selected_recipes() + if selected_recipes and ask: + lbl = "Package list may be incomplete!\nDo you want to build selected recipes" + lbl = lbl + " to get a full list (Yes) or just view the existing packages (No)?" + dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) + dialog.add_button(gtk.STOCK_YES, gtk.RESPONSE_YES) + dialog.add_button(gtk.STOCK_NO, gtk.RESPONSE_NO) + dialog.set_default_response(gtk.RESPONSE_YES) + response = dialog.run() + dialog.destroy() + if response == gtk.RESPONSE_YES: + self.switch_page(self.PACKAGE_GENERATING) + else: + self.switch_page(self.PACKAGE_SELECTION) + else: + self.switch_page(self.PACKAGE_SELECTION) + + def show_recipes(self): + self.switch_page(self.RECIPE_SELECTION) + + def initiate_new_build(self): + self.configuration.curr_mach = "" + self.image_configuration_page.switch_machine_combo() + self.switch_page(self.MACHINE_SELECTION) + + def show_configuration(self): + self.switch_page(self.RCPPKGINFO_POPULATED) + + def stop_build(self): + if self.stopping: + lbl = "Force Stop build?\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 = "Stop build?\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) diff --git a/bitbake/lib/bb/ui/crumbs/configurator.py b/bitbake/lib/bb/ui/crumbs/configurator.py deleted file mode 100644 index 837ee1ca91..0000000000 --- a/bitbake/lib/bb/ui/crumbs/configurator.py +++ /dev/null @@ -1,346 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011 Intel Corporation -# -# Authored by Joshua Lock -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import gobject -import copy -import re, os -from bb import data - -class Configurator(gobject.GObject): - - """ - A GObject to handle writing modified configuration values back - to conf files. - """ - __gsignals__ = { - "layers-loaded" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - "layers-changed" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()) - } - - def __init__(self): - gobject.GObject.__init__(self) - self.bblayers = None - self.enabled_layers = {} - self.loaded_layers = {} - self.config = {} - self.orig_config = {} - self.preconf = None - self.postconf = None - - # NOTE: cribbed from the cooker... - def _parse(self, f, data, include=False): - try: - return bb.parse.handle(f, data, include) - except (IOError, bb.parse.ParseError) as exc: - parselog.critical("Unable to parse %s: %s" % (f, exc)) - sys.exit(1) - - def _loadConf(self, path): - def getString(var): - return data.getVar(var, True) or "" - - if self.orig_config: - del self.orig_config - self.orig_config = {} - - data = bb.data.init() - data = self._parse(path, data) - - # We only need to care about certain variables - mach = getString('MACHINE') - if mach and mach != self.config.get('MACHINE', ''): - self.config['MACHINE'] = mach - sdkmach = getString('SDKMACHINE') - if sdkmach and sdkmach != self.config.get('SDKMACHINE', ''): - self.config['SDKMACHINE'] = sdkmach - distro = getString('DISTRO') - if not distro: - distro = "defaultsetup" - if distro and distro != self.config.get('DISTRO', ''): - self.config['DISTRO'] = distro - bbnum = getString('BB_NUMBER_THREADS') - if bbnum and bbnum != self.config.get('BB_NUMBER_THREADS', ''): - self.config['BB_NUMBER_THREADS'] = bbnum - pmake = getString('PARALLEL_MAKE') - if pmake and pmake != self.config.get('PARALLEL_MAKE', ''): - self.config['PARALLEL_MAKE'] = pmake - pclass = getString('PACKAGE_CLASSES') - if pclass and pclass != self.config.get('PACKAGE_CLASSES', ''): - self.config['PACKAGE_CLASSES'] = pclass - fstypes = getString('IMAGE_FSTYPES') - if fstypes and fstypes != self.config.get('IMAGE_FSTYPES', ''): - self.config['IMAGE_FSTYPES'] = fstypes - - # Values which aren't always set in the conf must be explicitly - # loaded as empty values for save to work - incompat = getString('INCOMPATIBLE_LICENSE') - if incompat and incompat != self.config.get('INCOMPATIBLE_LICENSE', ''): - self.config['INCOMPATIBLE_LICENSE'] = incompat - else: - self.config['INCOMPATIBLE_LICENSE'] = "" - - # Non-standard, namespaces, variables for GUI preferences - toolchain = getString('HOB_BUILD_TOOLCHAIN') - if toolchain and toolchain != self.config.get('HOB_BUILD_TOOLCHAIN', ''): - self.config['HOB_BUILD_TOOLCHAIN'] = toolchain - header = getString('HOB_BUILD_TOOLCHAIN_HEADERS') - if header and header != self.config.get('HOB_BUILD_TOOLCHAIN_HEADERS', ''): - self.config['HOB_BUILD_TOOLCHAIN_HEADERS'] = header - - self.orig_config = copy.deepcopy(self.config) - - def setConfVar(self, var, val): - self.config[var] = val - - def getConfVar(self, var): - if var in self.config: - return self.config[var] - else: - return "" - - def _loadLayerConf(self, path): - self.bblayers = path - self.enabled_layers = {} - self.loaded_layers = {} - data = bb.data.init() - data = self._parse(self.bblayers, data) - layers = (data.getVar('BBLAYERS', True) or "").split() - for layer in layers: - # TODO: we may be better off calling the layer by its - # BBFILE_COLLECTIONS value? - name = self._getLayerName(layer) - self.loaded_layers[name] = layer - - self.enabled_layers = copy.deepcopy(self.loaded_layers) - self.emit("layers-loaded") - - def _addConfigFile(self, path): - conffiles = ["local.conf", "hob-pre.conf", "hob-post.conf"] - pref, sep, filename = path.rpartition("/") - - if filename == "hob-pre.conf": - self.preconf = path - - if filename == "hob-post.conf": - self.postconf = path - - if filename in conffiles: - self._loadConf(path) - elif filename == "bblayers.conf": - self._loadLayerConf(path) - - def _splitLayer(self, path): - # we only care about the path up to /conf/layer.conf - layerpath, conf, end = path.rpartition("/conf/") - return layerpath - - def _getLayerName(self, path): - # Should this be the collection name? - layerpath, sep, name = path.rpartition("/") - return name - - def disableLayer(self, layer): - if layer in self.enabled_layers: - del self.enabled_layers[layer] - - def addLayerConf(self, confpath): - layerpath = self._splitLayer(confpath) - name = self._getLayerName(layerpath) - - if not layerpath or not name: - return None, None - elif name not in self.enabled_layers: - self.addLayer(name, layerpath) - return name, layerpath - else: - return name, None - - def addLayer(self, name, path): - self.enabled_layers[name] = path - - def _isLayerConfDirty(self): - # if a different number of layers enabled to what was - # loaded, definitely different - if len(self.enabled_layers) != len(self.loaded_layers): - return True - - for layer in self.loaded_layers: - # if layer loaded but no longer present, definitely dirty - if layer not in self.enabled_layers: - return True - - for layer in self.enabled_layers: - # if this layer wasn't present at load, definitely dirty - if layer not in self.loaded_layers: - return True - # if this layers path has changed, definitely dirty - if self.enabled_layers[layer] != self.loaded_layers[layer]: - return True - - return False - - def _constructLayerEntry(self): - """ - Returns a string representing the new layer selection - """ - layers = self.enabled_layers.copy() - # Construct BBLAYERS entry - layer_entry = "BBLAYERS = \" \\\n" - if 'meta' in layers: - layer_entry = layer_entry + " %s \\\n" % layers['meta'] - del layers['meta'] - for layer in layers: - layer_entry = layer_entry + " %s \\\n" % layers[layer] - layer_entry = layer_entry + " \"" - - return "".join(layer_entry) - - def writeConfFile(self, conffile, contents): - """ - Make a backup copy of conffile and write a new file in its stead with - the lines in the contents list. - """ - # Create a backup of the conf file - bkup = "%s~" % conffile - os.rename(conffile, bkup) - - # Write the contents list object to the conf file - with open(conffile, "w") as new: - new.write("".join(contents)) - - def updateConf(self, orig_lines, changed_values): - new_config_lines = [] - for var in changed_values: - # Convenience function for re.subn(). If the pattern matches - # return a string which contains an assignment using the same - # assignment operator as the old assignment. - def replace_val(matchobj): - var = matchobj.group(1) # config variable - op = matchobj.group(2) # assignment operator - val = changed_values[var] # new config value - return "%s %s \"%s\"" % (var, op, val) - - pattern = '^\s*(%s)\s*([+=?.]+)(.*)' % re.escape(var) - p = re.compile(pattern) - cnt = 0 - replaced = False - - # Iterate over the local.conf lines and if they are a match - # for the pattern comment out the line and append a new line - # with the new VAR op "value" entry - for line in orig_lines: - new_line, replacements = p.subn(replace_val, line) - if replacements: - orig_lines[cnt] = "#%s" % line - new_config_lines.append(new_line) - replaced = True - cnt = cnt + 1 - - if not replaced: - new_config_lines.append("%s = \"%s\"\n" % (var, changed_values[var])) - - # Add the modified variables - orig_lines.extend(new_config_lines) - return orig_lines - - def writeConf(self): - pre_vars = ["MACHINE", "SDKMACHINE", "DISTRO", - "INCOMPATIBLE_LICENSE"] - post_vars = ["BB_NUMBER_THREADS", "PARALLEL_MAKE", "PACKAGE_CLASSES", - "IMAGE_FSTYPES", "HOB_BUILD_TOOLCHAIN", - "HOB_BUILD_TOOLCHAIN_HEADERS"] - pre_values = {} - post_values = {} - changed_values = {} - pre_lines = None - post_lines = None - - for var in self.config: - val = self.config[var] - if self.orig_config.get(var, None) != val: - changed_values[var] = val - - if not len(changed_values): - return - - for var in changed_values: - if var in pre_vars: - pre_values[var] = changed_values[var] - elif var in post_vars: - post_values[var] = changed_values[var] - - with open(self.preconf, 'r') as pre: - pre_lines = pre.readlines() - pre_lines = self.updateConf(pre_lines, pre_values) - if len(pre_lines): - self.writeConfFile(self.preconf, pre_lines) - - with open(self.postconf, 'r') as post: - post_lines = post.readlines() - post_lines = self.updateConf(post_lines, post_values) - if len(post_lines): - self.writeConfFile(self.postconf, post_lines) - - del self.orig_config - self.orig_config = copy.deepcopy(self.config) - - def insertTempBBPath(self, bbpath, bbfiles): - # read the original conf into a list - with open(self.postconf, 'r') as config: - config_lines = config.readlines() - - if bbpath: - config_lines.append("BBPATH := \"${BBPATH}:%s\"\n" % bbpath) - if bbfiles: - config_lines.append("BBFILES := \"${BBFILES} %s\"\n" % bbfiles) - - self.writeConfFile(self.postconf, config_lines) - - def writeLayerConf(self): - # If we've not added/removed new layers don't write - if not self._isLayerConfDirty(): - return - - # This pattern should find the existing BBLAYERS - pattern = 'BBLAYERS\s=\s\".*\"' - - replacement = self._constructLayerEntry() - - with open(self.bblayers, "r") as f: - contents = f.read() - p = re.compile(pattern, re.DOTALL) - new = p.sub(replacement, contents) - - self.writeConfFile(self.bblayers, new) - - # set loaded_layers for dirtiness tracking - self.loaded_layers = copy.deepcopy(self.enabled_layers) - - self.emit("layers-changed") - - def configFound(self, handler, path): - self._addConfigFile(path) - - def loadConfig(self, path): - self._addConfigFile(path) diff --git a/bitbake/lib/bb/ui/crumbs/hig.py b/bitbake/lib/bb/ui/crumbs/hig.py index b3b3c7a72e..89dfe03608 100644 --- a/bitbake/lib/bb/ui/crumbs/hig.py +++ b/bitbake/lib/bb/ui/crumbs/hig.py @@ -1,9 +1,11 @@ # # BitBake Graphical GTK User Interface # -# Copyright (C) 2011 Intel Corporation +# Copyright (C) 2011-2012 Intel Corporation # # Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang # # 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,21 +20,33 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import gobject import gtk +import gobject +import hashlib +import os +import re +import subprocess +import shlex +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.hobwidget import HobWidget +from bb.ui.crumbs.progressbar import HobProgressBar + """ The following are convenience classes for implementing GNOME HIG compliant BitBake GUI's In summary: spacing = 12px, border-width = 6px """ +# +# CrumbsDialog +# class CrumbsDialog(gtk.Dialog): """ A GNOME HIG compliant dialog widget. Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons """ def __init__(self, parent=None, label="", icon=gtk.STOCK_INFO): - gtk.Dialog.__init__(self, "", parent, gtk.DIALOG_DESTROY_WITH_PARENT) + super(CrumbsDialog, self).__init__("", parent, gtk.DIALOG_DESTROY_WITH_PARENT) #self.set_property("has-separator", False) # note: deprecated in 2.22 @@ -59,3 +73,572 @@ class CrumbsDialog(gtk.Dialog): self.label.set_property("yalign", 0.00) self.label.show() first_row.add(self.label) + +# +# Brought-in-by Dialog +# +class BinbDialog(gtk.Dialog): + """ + A dialog widget to show "brought in by" info when a recipe/package is clicked. + """ + + def __init__(self, title, content, parent=None): + super(BinbDialog, self).__init__(title, parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, None) + + self.set_position(gtk.WIN_POS_MOUSE) + self.set_resizable(False) + self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.DARK)) + + hbox = gtk.HBox(False, 0) + self.vbox.pack_start(hbox, expand=False, fill=False, padding=10) + + label = gtk.Label(content) + label.set_alignment(0, 0) + label.set_line_wrap(True) + label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.WHITE)) + + hbox.pack_start(label, expand=False, fill=False, padding=10) + self.vbox.show_all() + +# +# AdvancedSettings Dialog +# +class AdvancedSettingDialog (gtk.Dialog): + + def __init__(self, title, configuration, all_image_types, + all_package_formats, all_distros, all_sdk_machines, + max_threads, split_model, parent, flags, buttons): + super(AdvancedSettingDialog, self).__init__(title, parent, flags, buttons) + + # class members from other objects + # bitbake settings from Builder.Configuration + self.configuration = configuration + self.image_types = all_image_types + self.all_package_formats = all_package_formats + self.all_distros = all_distros + self.all_sdk_machines = all_sdk_machines + self.max_threads = max_threads + self.split_model = split_model + + # class members for internal use + self.pkgfmt_store = None + self.distro_combo = None + self.dldir_text = None + self.sstatedir_text = None + self.sstatemirror_text = None + self.bb_spinner = None + self.pmake_spinner = None + self.rootfs_size_spinner = None + self.extra_size_spinner = None + self.gplv3_checkbox = None + self.toolchain_checkbox = None + self.setting_store = None + self.image_types_checkbuttons = {} + + self.variables = {} + self.variables["PACKAGE_FORMAT"] = self.configuration.curr_package_format + self.variables["INCOMPATIBLE_LICENSE"] = self.configuration.incompat_license + self.variables["IMAGE_FSTYPES"] = self.configuration.image_fstypes + self.md5 = hashlib.md5(str(sorted(self.variables.items()))).hexdigest() + self.settings_changed = False + + # create visual elements on the dialog + self.create_visual_elements() + self.connect("response", self.response_cb) + + def create_visual_elements(self): + self.set_size_request(500, 500) + + self.nb = gtk.Notebook() + self.nb.set_show_tabs(True) + self.nb.append_page(self.create_image_types_page(), gtk.Label("Image types")) + self.nb.append_page(self.create_output_page(), gtk.Label("Output")) + self.nb.append_page(self.create_build_environment_page(), gtk.Label("Build environment")) + self.nb.append_page(self.create_others_page(), gtk.Label("Others")) + self.nb.set_current_page(0) + self.vbox.pack_start(self.nb, expand=True, fill=True) + self.vbox.pack_end(gtk.HSeparator(), expand=True, fill=True) + + self.show_all() + + def create_image_types_page(self): + advanced_vbox = gtk.VBox(False, 15) + advanced_vbox.set_border_width(20) + + rows = (len(self.image_types)+1)/2 + table = gtk.Table(rows + 1, 10, True) + advanced_vbox.pack_start(table, expand=False, fill=False) + + tooltip = "Select image file system types that will be used." + image = gtk.Image() + image.show() + image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON) + image.set_tooltip_text(tooltip) + label = HobWidget.gen_label_widget("Select image types:") + table.attach(label, 0, 9, 0, 1) + table.attach(image, 9, 10, 0, 1) + + i = 1 + j = 1 + for image_type in self.image_types: + self.image_types_checkbuttons[image_type] = gtk.CheckButton(image_type) + self.image_types_checkbuttons[image_type].set_tooltip_text("Build an %s image" % image_type) + table.attach(self.image_types_checkbuttons[image_type], j, j + 4, i, i + 1) + if image_type in self.configuration.image_fstypes: + self.image_types_checkbuttons[image_type].set_active(True) + i += 1 + if i > rows: + i = 1 + j = j + 4 + + return advanced_vbox + + def create_output_page(self): + advanced_vbox = gtk.VBox(False, 15) + advanced_vbox.set_border_width(20) + + sub_vbox = gtk.VBox(False, 5) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = HobWidget.gen_label_widget("Packaging Format:") + tooltip = "Select package formats that will be used. " + tooltip += "The first format will be used for final image" + pkgfmt_widget, self.pkgfmt_store = HobWidget.gen_pkgfmt_widget(self.configuration.curr_package_format, self.all_package_formats, tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(pkgfmt_widget, expand=False, fill=False) + + sub_vbox = gtk.VBox(False, 5) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = HobWidget.gen_label_widget("Image Rootfs Size: (MB)") + tooltip = "Sets the size of your target image.\nThis is the basic size of your target image, unless your selected package size exceeds this value, or you set value to \"Image Extra Size\"." + rootfs_size_widget, self.rootfs_size_spinner = HobWidget.gen_spinner_widget(int(self.configuration.image_rootfs_size*1.0/1024), 0, 1024, tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(rootfs_size_widget, expand=False, fill=False) + + sub_vbox = gtk.VBox(False, 5) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = HobWidget.gen_label_widget("Image Extra Size: (MB)") + tooltip = "Sets the extra free space of your target image.\nDefaultly, system will reserve 30% of your image size as your free space. If your image contains zypper, it will bring in 50MB more space. The maximum free space is 1024MB." + extra_size_widget, self.extra_size_spinner = HobWidget.gen_spinner_widget(int(self.configuration.image_extra_size*1.0/1024), 0, 1024, tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(extra_size_widget, expand=False, fill=False) + + self.gplv3_checkbox = gtk.CheckButton("Exclude GPLv3 packages") + self.gplv3_checkbox.set_tooltip_text("Check this box to prevent GPLv3 packages from being included in your image") + if "GPLv3" in self.configuration.incompat_license.split(): + self.gplv3_checkbox.set_active(True) + else: + self.gplv3_checkbox.set_active(False) + advanced_vbox.pack_start(self.gplv3_checkbox, expand=False, fill=False) + + sub_hbox = gtk.HBox(False, 5) + advanced_vbox.pack_start(sub_hbox, expand=False, fill=False) + self.toolchain_checkbox = gtk.CheckButton("Build Toolchain") + self.toolchain_checkbox.set_tooltip_text("Check this box to build the related toolchain with your image") + self.toolchain_checkbox.set_active(self.configuration.toolchain_build) + sub_hbox.pack_start(self.toolchain_checkbox, expand=False, fill=False) + + tooltip = "This is the Host platform you would like to run the toolchain" + sdk_machine_widget, self.sdk_machine_combo = HobWidget.gen_combo_widget(self.configuration.curr_sdk_machine, self.all_sdk_machines, tooltip) + sub_hbox.pack_start(sdk_machine_widget, expand=False, fill=False) + + return advanced_vbox + + def create_build_environment_page(self): + advanced_vbox = gtk.VBox(False, 15) + advanced_vbox.set_border_width(20) + + sub_vbox = gtk.VBox(False, 5) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = HobWidget.gen_label_widget("Select Distro:") + tooltip = "This is the Yocto distribution you would like to use" + distro_widget, self.distro_combo = HobWidget.gen_combo_widget(self.configuration.curr_distro, self.all_distros, tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(distro_widget, expand=False, fill=False) + + sub_vbox = gtk.VBox(False, 5) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = HobWidget.gen_label_widget("BB_NUMBER_THREADS:") + tooltip = "Sets the number of threads that bitbake tasks can run simultaneously" + bbthread_widget, self.bb_spinner = HobWidget.gen_spinner_widget(self.configuration.bbthread, 1, self.max_threads, tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(bbthread_widget, expand=False, fill=False) + + sub_vbox = gtk.VBox(False, 5) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = HobWidget.gen_label_widget("PARALLEL_MAKE:") + tooltip = "Sets the make parallism, as known as 'make -j'" + pmake_widget, self.pmake_spinner = HobWidget.gen_spinner_widget(self.configuration.pmake, 1, self.max_threads, tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(pmake_widget, expand=False, fill=False) + + sub_vbox = gtk.VBox(False, 5) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = HobWidget.gen_label_widget("Set Download Directory:") + tooltip = "Select a folder that caches the upstream project source code" + dldir_widget, self.dldir_text = HobWidget.gen_entry_widget(self.split_model, self.configuration.dldir, self, tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(dldir_widget, expand=False, fill=False) + + sub_vbox = gtk.VBox(False, 5) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = HobWidget.gen_label_widget("Select SSTATE Directory:") + tooltip = "Select a folder that caches your prebuilt results" + sstatedir_widget, self.sstatedir_text = HobWidget.gen_entry_widget(self.split_model, self.configuration.sstatedir, self, tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(sstatedir_widget, expand=False, fill=False) + + sub_vbox = gtk.VBox(False, 5) + advanced_vbox.pack_start(sub_vbox, expand=False, fill=False) + label = HobWidget.gen_label_widget("Select SSTATE Mirror:") + tooltip = "Select the prebuilt mirror that will fasten your build speed" + sstatemirror_widget, self.sstatemirror_text = HobWidget.gen_entry_widget(self.split_model, self.configuration.sstatemirror, self, tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(sstatemirror_widget, expand=False, fill=False) + + return advanced_vbox + + def create_others_page(self): + advanced_vbox = gtk.VBox(False, 15) + advanced_vbox.set_border_width(20) + + sub_vbox = gtk.VBox(False, 5) + advanced_vbox.pack_start(sub_vbox, expand=True, fill=True) + label = HobWidget.gen_label_widget("Add your own variables:") + tooltip = "This is the key/value pair for your extra settings" + setting_widget, self.setting_store = HobWidget.gen_editable_settings(self.configuration.extra_setting, tooltip) + sub_vbox.pack_start(label, expand=False, fill=False) + sub_vbox.pack_start(setting_widget, expand=True, fill=True) + + return advanced_vbox + + def response_cb(self, dialog, response_id): + self.variables = {} + + self.configuration.curr_package_format = "" + it = self.pkgfmt_store.get_iter_first() + while it: + value = self.pkgfmt_store.get_value(it, 2) + if value: + self.configuration.curr_package_format += (self.pkgfmt_store.get_value(it, 1) + " ") + it = self.pkgfmt_store.iter_next(it) + self.configuration.curr_package_format = self.configuration.curr_package_format.strip() + self.variables["PACKAGE_FORMAT"] = self.configuration.curr_package_format + + self.configuration.curr_distro = self.distro_combo.get_active_text() + self.configuration.dldir = self.dldir_text.get_text() + self.configuration.sstatedir = self.sstatedir_text.get_text() + self.configuration.sstatemirror = self.sstatemirror_text.get_text() + self.configuration.bbthread = self.bb_spinner.get_value_as_int() + self.configuration.pmake = self.pmake_spinner.get_value_as_int() + self.configuration.image_rootfs_size = self.rootfs_size_spinner.get_value_as_int() * 1024 + self.configuration.image_extra_size = self.extra_size_spinner.get_value_as_int() * 1024 + + self.configuration.image_fstypes = [] + for image_type in self.image_types: + if self.image_types_checkbuttons[image_type].get_active(): + self.configuration.image_fstypes.append(image_type) + self.variables["IMAGE_FSTYPES"] = self.configuration.image_fstypes + + if self.gplv3_checkbox.get_active(): + if "GPLv3" not in self.configuration.incompat_license.split(): + self.configuration.incompat_license += " GPLv3" + else: + if "GPLv3" in self.configuration.incompat_license.split(): + self.configuration.incompat_license = self.configuration.incompat_license.split().remove("GPLv3") + self.configuration.incompat_license = " ".join(self.configuration.incompat_license or []) + self.configuration.incompat_license = self.configuration.incompat_license.strip() + self.variables["INCOMPATIBLE_LICENSE"] = self.configuration.incompat_license + + self.configuration.toolchain_build = self.toolchain_checkbox.get_active() + + self.configuration.extra_setting = {} + it = self.setting_store.get_iter_first() + while it: + key = self.setting_store.get_value(it, 0) + value = self.setting_store.get_value(it, 1) + self.configuration.extra_setting[key] = value + self.variables[key] = value + it = self.setting_store.iter_next(it) + + md5 = hashlib.md5(str(sorted(self.variables.items()))).hexdigest() + self.settings_changed = (self.md5 != md5) + +# +# DeployImageDialog +# +class DeployImageDialog (gtk.Dialog): + + __dummy_usb__ = "--select a usb drive--" + + def __init__(self, title, image_path, parent, flags, buttons): + super(DeployImageDialog, self).__init__(title, parent, flags, buttons) + + self.image_path = image_path + + self.create_visual_elements() + self.connect("response", self.response_cb) + + def create_visual_elements(self): + self.set_border_width(20) + self.set_default_size(500, 250) + + label = gtk.Label() + label.set_alignment(0.0, 0.5) + markup = "The image to be written into usb drive:" + label.set_markup(markup) + self.vbox.pack_start(label, expand=False, fill=False, padding=2) + + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scroll.set_shadow_type(gtk.SHADOW_IN) + tv = gtk.TextView() + tv.set_editable(False) + tv.set_wrap_mode(gtk.WRAP_WORD) + tv.set_cursor_visible(False) + buf = gtk.TextBuffer() + buf.set_text(self.image_path) + tv.set_buffer(buf) + scroll.add(tv) + self.vbox.pack_start(scroll, expand=True, fill=True) + + self.usb_desc = gtk.Label() + self.usb_desc.set_alignment(0.0, 0.5) + markup = "You haven't chosen any USB drive." + self.usb_desc.set_markup(markup) + + self.usb_combo = gtk.combo_box_new_text() + self.usb_combo.connect("changed", self.usb_combo_changed_cb) + model = self.usb_combo.get_model() + model.clear() + self.usb_combo.append_text(self.__dummy_usb__) + for usb in self.find_all_usb_devices(): + self.usb_combo.append_text("/dev/" + usb) + self.usb_combo.set_active(0) + self.vbox.pack_start(self.usb_combo, expand=True, fill=True) + self.vbox.pack_start(self.usb_desc, expand=False, fill=False, padding=2) + + self.progress_bar = HobProgressBar() + self.vbox.pack_start(self.progress_bar, expand=False, fill=False) + separator = gtk.HSeparator() + self.vbox.pack_start(separator, expand=False, fill=True, padding=10) + + self.vbox.show_all() + self.progress_bar.hide() + + def popen_read(self, cmd): + return os.popen("%s 2>/dev/null" % cmd).read().strip() + + def find_all_usb_devices(self): + usb_devs = [ os.readlink(u) + for u in self.popen_read('ls /dev/disk/by-id/usb*').split() + if not re.search(r'part\d+', u) ] + return [ '%s' % u[u.rfind('/')+1:] for u in usb_devs ] + + def get_usb_info(self, dev): + return "%s %s" % \ + (self.popen_read('cat /sys/class/block/%s/device/vendor' % dev), + self.popen_read('cat /sys/class/block/%s/device/model' % dev)) + + def usb_combo_changed_cb(self, usb_combo): + combo_item = self.usb_combo.get_active_text() + if not combo_item or combo_item == self.__dummy_usb__: + markup = "You haven't chosen any USB drive." + self.usb_desc.set_markup(markup) + else: + markup = "" + self.get_usb_info(combo_item.lstrip("/dev/")) + "" + self.usb_desc.set_markup(markup) + + def response_cb(self, dialog, response_id): + if response_id == gtk.RESPONSE_YES: + combo_item = self.usb_combo.get_active_text() + if combo_item and combo_item != self.__dummy_usb__: + cmdline = "/usr/bin/xterm -e " + cmdline += "\"sudo dd if=" + self.image_path + " of=" + combo_item + "; bash\"" + subprocess.Popen(args=shlex.split(cmdline)) + + def update_progress_bar(self, title, fraction, status=True): + self.progress_bar.update(fraction) + self.progress_bar.set_title(title) + self.progress_bar.set_rcstyle(status) + + def write_file(self, ifile, ofile): + self.progress_bar.reset() + self.progress_bar.show() + + f_from = os.open(ifile, os.O_RDONLY) + f_to = os.open(ofile, os.O_WRONLY) + + total_size = os.stat(ifile).st_size + written_size = 0 + + while True: + buf = os.read(f_from, 1024*1024) + if not buf: + break + os.write(f_to, buf) + written_size += 1024*1024 + self.update_progress_bar("Writing to usb:", written_size * 1.0/total_size) + + self.update_progress_bar("Writing completed:", 1.0) + os.close(f_from) + os.close(f_to) + self.progress_bar.hide() +# +# LayerSelectionDialog +# +class LayerSelectionDialog (gtk.Dialog): + + def __init__(self, title, layers, all_layers, split_model, + parent, flags, buttons): + super(LayerSelectionDialog, self).__init__(title, parent, flags, buttons) + + # class members from other objects + self.layers = layers + self.all_layers = all_layers + self.split_model = split_model + self.layers_changed = False + + # class members for internal use + self.layer_store = None + + # create visual elements on the dialog + self.create_visual_elements() + self.connect("response", self.response_cb) + + def create_visual_elements(self): + self.set_border_width(20) + self.set_default_size(400, 250) + + hbox_top = gtk.HBox() + self.set_border_width(12) + self.vbox.pack_start(hbox_top, expand=False, fill=False) + + if self.split_model: + label = HobWidget.gen_label_widget("Select Layers:\n(Available layers under '${COREBASE}/layers/' directory)") + else: + label = HobWidget.gen_label_widget("Select Layers:") + hbox_top.pack_start(label, expand=False, fill=False) + + tooltip = "Layer is a collection of bb files and conf files" + image = gtk.Image() + image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON) + image.set_tooltip_text(tooltip) + hbox_top.pack_end(image, expand=False, fill=False) + + layer_widget, self.layer_store = HobWidget.gen_layer_widget(self.split_model, self.layers, self.all_layers, self, None) + + self.vbox.pack_start(layer_widget, expand=True, fill=True) + + separator = gtk.HSeparator() + self.vbox.pack_start(separator, False, True, 5) + separator.show() + + hbox_button = gtk.HBox() + self.vbox.pack_end(hbox_button, expand=False, fill=False) + hbox_button.show() + + label = HobWidget.gen_label_widget("'meta' is Core layer for Yocto images\n" + "Please do not remove it") + hbox_button.pack_start(label, expand=False, fill=False) + + self.show_all() + + def response_cb(self, dialog, response_id): + model = self.layer_store + it = model.get_iter_first() + layers = [] + while it: + if self.split_model: + inc = model.get_value(it, 1) + if inc: + layers.append(model.get_value(it, 0)) + else: + layers.append(model.get_value(it, 0)) + it = model.iter_next(it) + + self.layers_changed = (self.layers != layers) + self.layers = layers + +class ImageSelectionDialog (gtk.Dialog): + + def __init__(self, image_folder, image_types, title, parent, flags, buttons): + super(ImageSelectionDialog, self).__init__(title, parent, flags, buttons) + self.connect("response", self.response_cb) + + self.image_folder = image_folder + self.image_types = image_types + self.image_names = [] + + # create visual elements on the dialog + self.create_visual_elements() + + self.image_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN) + self.fill_image_store() + + def create_visual_elements(self): + self.set_border_width(20) + self.set_default_size(600, 300) + self.vbox.set_spacing(10) + + hbox = gtk.HBox(False, 10) + self.vbox.pack_start(hbox, expand=False, fill=False) + + entry = gtk.Entry() + entry.set_text(self.image_folder) + table = gtk.Table(1, 10, True) + table.set_size_request(560, -1) + hbox.pack_start(table, expand=False, fill=False) + table.attach(entry, 0, 9, 0, 1) + image = gtk.Image() + image.set_from_stock(gtk.STOCK_OPEN,gtk.ICON_SIZE_BUTTON) + open_button = gtk.Button() + open_button.set_image(image) + open_button.connect("clicked", self.select_path_cb, self, entry) + table.attach(open_button, 9, 10, 0, 1) + + imgtv_widget, self.imgsel_tv = HobWidget.gen_imgtv_widget(400, 160) + self.vbox.pack_start(imgtv_widget, expand=True, fill=True) + + self.show_all() + + def select_path_cb(self, action, parent, entry): + dialog = gtk.FileChooserDialog("", parent, + gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, + (gtk.STOCK_OK, gtk.RESPONSE_YES, + gtk.STOCK_CANCEL, gtk.RESPONSE_NO)) + response = dialog.run() + if response == gtk.RESPONSE_YES: + path = dialog.get_filename() + entry.set_text(path) + self.image_folder = path + self.fill_image_store() + + dialog.destroy() + + def fill_image_store(self): + self.image_store.clear() + imageset = set() + for root, dirs, files in os.walk(self.image_folder): + for f in files: + for image_type in self.image_types: + if f.endswith('.' + image_type): + imageset.add(f.rsplit('.' + image_type)[0]) + + for image in imageset: + self.image_store.set(self.image_store.append(), 0, image, 1, False) + + self.imgsel_tv.set_model(self.image_store) + + def response_cb(self, dialog, response_id): + self.image_names = [] + if response_id == gtk.RESPONSE_YES: + iter = self.image_store.get_iter_first() + while iter: + path = self.image_store.get_path(iter) + if self.image_store[path][1]: + for root, dirs, files in os.walk(self.image_folder): + for f in files: + if f.startswith(self.image_store[path][0] + '.'): + self.image_names.append(f) + break + iter = self.image_store.iter_next(iter) diff --git a/bitbake/lib/bb/ui/crumbs/hobcolor.py b/bitbake/lib/bb/ui/crumbs/hobcolor.py new file mode 100644 index 0000000000..9d67d5c496 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hobcolor.py @@ -0,0 +1,35 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Shane Wang +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +class HobColors: + WHITE = "#ffffff" + PALE_GREEN = "#aaffaa" + ORANGE = "#ff7c24" + PALE_RED = "#ffaaaa" + GRAY = "#aaaaaa" + LIGHT_GRAY = "#dddddd" + DARK = "#3c3b37" + BLACK = "#000000" + LIGHT_ORANGE = "#f7a787" + + OK = WHITE + RUNNING = PALE_GREEN + WARNING = ORANGE + ERROR = PALE_RED diff --git a/bitbake/lib/bb/ui/crumbs/hobeventhandler.py b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py index ddab987ca8..b071ad4503 100644 --- a/bitbake/lib/bb/ui/crumbs/hobeventhandler.py +++ b/bitbake/lib/bb/ui/crumbs/hobeventhandler.py @@ -4,6 +4,7 @@ # Copyright (C) 2011 Intel Corporation # # Authored by Joshua Lock +# Authored by Dongxiao Xu # # 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 @@ -20,10 +21,7 @@ import gobject import logging -import tempfile -import datetime - -progress_total = 0 +from bb.ui.crumbs.runningbuild import RunningBuild class HobHandler(gobject.GObject): @@ -31,147 +29,176 @@ class HobHandler(gobject.GObject): This object does BitBake event handling for the hob gui. """ __gsignals__ = { - "machines-updated" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_PYOBJECT,)), - "sdk-machines-updated": (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_PYOBJECT,)), - "distros-updated" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_PYOBJECT,)), - "package-formats-found" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_PYOBJECT,)), - "config-found" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - "generating-data" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - "data-generated" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - "fatal-error" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_STRING, - gobject.TYPE_STRING,)), - "command-failed" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - "reload-triggered" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_STRING, - gobject.TYPE_STRING,)), + "layers-updated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + "package-formats-updated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + "config-updated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,)), + "command-succeeded" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_INT,)), + "command-failed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), + "generating-data" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + "data-generated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + "parsing-started" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + "parsing" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + "parsing-completed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), } - (CFG_PATH_LOCAL, CFG_PATH_PRE, CFG_PATH_POST, CFG_PATH_LAYERS, CFG_FILES_DISTRO, CFG_FILES_MACH, CFG_FILES_SDK, FILES_MATCH_CLASS, GENERATE_TGTS, REPARSE_FILES, BUILD_IMAGE) = range(11) + (CFG_AVAIL_LAYERS, CFG_PATH_LAYERS, CFG_FILES_DISTRO, CFG_FILES_MACH, CFG_FILES_SDKMACH, FILES_MATCH_CLASS, PARSE_CONFIG, PARSE_BBFILES, GENERATE_TGTS, GENERATE_PACKAGEINFO, BUILD_TARGET_RECIPES, BUILD_TARGET_IMAGE, CMD_END) = range(13) + (LAYERS_REFRESH, GENERATE_RECIPES, GENERATE_PACKAGES, GENERATE_IMAGE, POPULATE_PACKAGEINFO) = range(5) - def __init__(self, taskmodel, server): - gobject.GObject.__init__(self) + def __init__(self, server, server_addr, client_addr, recipe_model, package_model): + super(HobHandler, self).__init__() - self.current_command = None - self.building = False - self.build_toolchain = False - self.build_toolchain_headers = False + self.build = RunningBuild(sequential=True) + + self.recipe_model = recipe_model + self.package_model = package_model + + self.commands_async = [] self.generating = False - self.build_queue = [] self.current_phase = None - self.bbpath_ok = False - self.bbfiles_ok = False - self.build_type = "image" - self.image_dir = os.path.join(tempfile.gettempdir(), 'hob-images') + self.building = False + self.recipe_queue = [] + self.package_queue = [] - self.model = taskmodel self.server = server - - deploy_dir = self.server.runCommand(["getVariable", "DEPLOY_DIR"]) - self.image_out_dir = os.path.join(deploy_dir, "images") - self.image_output_types = self.server.runCommand(["getVariable", "IMAGE_FSTYPES"]).split(" ") - self.bbpath = self.server.runCommand(["getVariable", "BBPATH"]) - self.bbfiles = self.server.runCommand(["getVariable", "BBFILES"]) - - def run_next_command(self): - if self.current_command and not self.generating: + self.error_msg = "" + self.initcmd = None + + self.split_model = False + if server_addr and client_addr: + self.split_model = (server_addr != client_addr) + self.reset_server() # reset server if server was found just now + self.server_addr = server_addr + + def kick(self): + import xmlrpclib + try: + # kick the while thing off + if self.split_model: + self.commands_async.append(self.CFG_AVAIL_LAYERS) + else: + self.commands_async.append(self.CFG_PATH_LAYERS) + self.commands_async.append(self.CFG_FILES_DISTRO) + self.commands_async.append(self.CFG_FILES_MACH) + self.commands_async.append(self.CFG_FILES_SDKMACH) + self.commands_async.append(self.FILES_MATCH_CLASS) + self.run_next_command() + return True + except xmlrpclib.Fault as x: + print("XMLRPC Fault getting commandline:\n %s" % x) + return False + + def set_busy(self): + if not self.generating: self.emit("generating-data") self.generating = True - if self.current_command == self.CFG_PATH_LOCAL: - self.current_command = self.CFG_PATH_PRE - self.server.runCommand(["findConfigFilePath", "hob-pre.conf"]) - elif self.current_command == self.CFG_PATH_PRE: - self.current_command = self.CFG_PATH_POST - self.server.runCommand(["findConfigFilePath", "hob-post.conf"]) - elif self.current_command == self.CFG_PATH_POST: - self.current_command = self.CFG_PATH_LAYERS + def clear_busy(self): + if self.generating: + self.emit("data-generated") + self.generating = False + + def run_next_command(self, initcmd=None): + if initcmd != None: + self.initcmd = initcmd + + if self.commands_async: + self.set_busy() + next_command = self.commands_async.pop(0) + else: + self.clear_busy() + if self.initcmd != None: + self.emit("command-succeeded", self.initcmd) + return + + if next_command == self.CFG_AVAIL_LAYERS: + self.server.runCommand(["findCoreBaseFiles", "layers", "conf/layer.conf"]) + elif next_command == self.CFG_PATH_LAYERS: self.server.runCommand(["findConfigFilePath", "bblayers.conf"]) - elif self.current_command == self.CFG_PATH_LAYERS: - self.current_command = self.CFG_FILES_DISTRO + elif next_command == self.CFG_FILES_DISTRO: self.server.runCommand(["findConfigFiles", "DISTRO"]) - elif self.current_command == self.CFG_FILES_DISTRO: - self.current_command = self.CFG_FILES_MACH + elif next_command == self.CFG_FILES_MACH: self.server.runCommand(["findConfigFiles", "MACHINE"]) - elif self.current_command == self.CFG_FILES_MACH: - self.current_command = self.CFG_FILES_SDK + elif next_command == self.CFG_FILES_SDKMACH: self.server.runCommand(["findConfigFiles", "MACHINE-SDK"]) - elif self.current_command == self.CFG_FILES_SDK: - self.current_command = self.FILES_MATCH_CLASS + elif next_command == self.FILES_MATCH_CLASS: self.server.runCommand(["findFilesMatchingInDir", "rootfs_", "classes"]) - elif self.current_command == self.FILES_MATCH_CLASS: - self.current_command = self.GENERATE_TGTS - self.server.runCommand(["generateTargetsTree", "classes/image.bbclass"]) - elif self.current_command == self.GENERATE_TGTS: - if self.generating: - self.emit("data-generated") - self.generating = False - self.current_command = None - elif self.current_command == self.REPARSE_FILES: - if self.build_queue: - self.current_command = self.BUILD_IMAGE - else: - self.current_command = self.CFG_PATH_LAYERS - self.server.runCommand(["resetCooker"]) - self.server.runCommand(["reparseFiles"]) - elif self.current_command == self.BUILD_IMAGE: - if self.generating: - self.emit("data-generated") - self.generating = False + elif next_command == self.PARSE_CONFIG: + self.server.runCommand(["parseConfigurationFiles", "", ""]) + elif next_command == self.PARSE_BBFILES: + self.server.runCommand(["parseFiles"]) + elif next_command == self.GENERATE_TGTS: + self.server.runCommand(["generateTargetsTree", "classes/image.bbclass", [], True]) + elif next_command == self.GENERATE_PACKAGEINFO: + self.server.runCommand(["triggerEvent", "bb.event.RequestPackageInfo()"]) + elif next_command == self.BUILD_TARGET_RECIPES: + self.clear_busy() self.building = True - self.server.runCommand(["buildTargets", self.build_queue, "build"]) - self.build_queue = [] - self.current_command = None - - def handle_event(self, event, running_build, pbar): + self.server.runCommand(["buildTargets", self.recipe_queue, "build"]) + self.recipe_queue = [] + elif next_command == self.BUILD_TARGET_IMAGE: + self.clear_busy() + self.building = True + targets = ["hob-image"] + self.server.runCommand(["setVariable", "LINGUAS_INSTALL", ""]) + self.server.runCommand(["setVariable", "PACKAGE_INSTALL", " ".join(self.package_queue)]) + if self.toolchain_build: + pkgs = self.package_queue + [i+'-dev' for i in self.package_queue] + [i+'-dbg' for i in self.package_queue] + self.server.runCommand(["setVariable", "TOOLCHAIN_TARGET_TASK", " ".join(pkgs)]) + targets.append("hob-toolchain") + self.server.runCommand(["buildTargets", targets, "build"]) + + def handle_event(self, event): if not event: - return + return - # If we're running a build, use the RunningBuild event handler if self.building: self.current_phase = "building" - running_build.handle_event(event) + self.build.handle_event(event) + + if isinstance(event, bb.event.PackageInfo): + self.package_model.populate(event._pkginfolist) + self.run_next_command() + + elif(isinstance(event, logging.LogRecord)): + if event.levelno >= logging.ERROR: + self.error_msg += event.msg + '\n' + elif isinstance(event, bb.event.TargetsTreeGenerated): self.current_phase = "data generation" if event._model: - self.model.populate(event._model) + self.recipe_model.populate(event._model) + elif isinstance(event, bb.event.CoreBaseFilesFound): + self.current_phase = "configuration lookup" + paths = event._paths + self.emit('layers-updated', paths) elif isinstance(event, bb.event.ConfigFilesFound): self.current_phase = "configuration lookup" var = event._variable - if var == "distro": - distros = event._values - distros.sort() - self.emit("distros-updated", distros) - elif var == "machine": - machines = event._values - machines.sort() - self.emit("machines-updated", machines) - elif var == "machine-sdk": - sdk_machines = event._values - sdk_machines.sort() - self.emit("sdk-machines-updated", sdk_machines) + values = event._values + values.sort() + self.emit("config-updated", var, values) elif isinstance(event, bb.event.ConfigFilePathFound): self.current_phase = "configuration lookup" - path = event._path - self.emit("config-found", path) elif isinstance(event, bb.event.FilesMatchingFound): self.current_phase = "configuration lookup" # FIXME: hard coding, should at least be a variable shared between @@ -183,48 +210,84 @@ class HobHandler(gobject.GObject): fs, sep, format = classname.rpartition("_") formats.append(format) formats.sort() - self.emit("package-formats-found", formats) + self.emit("package-formats-updated", formats) elif isinstance(event, bb.command.CommandCompleted): self.current_phase = None self.run_next_command() + + elif isinstance(event, bb.event.NoProvider): + if event._runtime: + r = "R" + else: + r = "" + if event._dependees: + self.error_msg += " Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)" % (r, event._item, ", ".join(event._dependees), r) + else: + self.error_msg += " Nothing %sPROVIDES '%s'" % (r, event._item) + if event._reasons: + for reason in event._reasons: + self.error_msg += " %s" % reason + + self.commands_async = [] + self.emit("command-failed", self.error_msg) + self.error_msg = "" + elif isinstance(event, bb.command.CommandFailed): - self.emit("command-failed", event.error) - elif isinstance(event, bb.event.CacheLoadStarted): - self.current_phase = "cache loading" - bb.ui.crumbs.hobeventhandler.progress_total = event.total - pbar.set_text("Loading cache: %s/%s" % (0, bb.ui.crumbs.hobeventhandler.progress_total)) - elif isinstance(event, bb.event.CacheLoadProgress): - self.current_phase = "cache loading" - pbar.set_text("Loading cache: %s/%s" % (event.current, bb.ui.crumbs.hobeventhandler.progress_total)) - elif isinstance(event, bb.event.CacheLoadCompleted): - self.current_phase = None - pbar.set_text("Loading...") - elif isinstance(event, bb.event.ParseStarted): - self.current_phase = "recipe parsing" - if event.total == 0: - return - bb.ui.crumbs.hobeventhandler.progress_total = event.total - pbar.set_text("Processing recipes: %s/%s" % (0, bb.ui.crumbs.hobeventhandler.progress_total)) - elif isinstance(event, bb.event.ParseProgress): - self.current_phase = "recipe parsing" - pbar.set_text("Processing recipes: %s/%s" % (event.current, bb.ui.crumbs.hobeventhandler.progress_total)) - elif isinstance(event, bb.event.ParseCompleted): - self.current_phase = None - pbar.set_fraction(1.0) - pbar.set_text("Loading...") - elif isinstance(event, logging.LogRecord): - format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s") - if event.levelno >= format.CRITICAL: - self.emit("fatal-error", event.getMessage(), self.current_phase) + self.commands_async = [] + if self.error_msg: + self.emit("command-failed", self.error_msg) + self.error_msg = "" + elif isinstance(event, (bb.event.ParseStarted, + bb.event.CacheLoadStarted, + bb.event.TreeDataPreparationStarted, + )): + message = {} + message["eventname"] = bb.event.getName(event) + message["current"] = 0 + message["total"] = None + message["title"] = "Parsing recipes: " + self.emit("parsing-started", message) + elif isinstance(event, (bb.event.ParseProgress, + bb.event.CacheLoadProgress, + bb.event.TreeDataPreparationProgress)): + message = {} + message["eventname"] = bb.event.getName(event) + message["current"] = event.current + message["total"] = event.total + message["title"] = "Parsing recipes: " + self.emit("parsing", message) + elif isinstance(event, (bb.event.ParseCompleted, + bb.event.CacheLoadCompleted, + bb.event.TreeDataPreparationCompleted)): + message = {} + message["eventname"] = bb.event.getName(event) + message["current"] = event.total + message["total"] = event.total + message["title"] = "Parsing recipes: " + self.emit("parsing-completed", message) + return - def event_handle_idle_func (self, eventHandler, running_build, pbar): - # Consume as many messages as we can in the time available to us - event = eventHandler.getEvent() - while event: - self.handle_event(event, running_build, pbar) - event = eventHandler.getEvent() - return True + def init_cooker(self): + self.server.runCommand(["initCooker"]) + + def refresh_layers(self, bblayers): + self.server.runCommand(["initCooker"]) + self.server.runCommand(["setVariable", "BBLAYERS", " ".join(bblayers)]) + self.commands_async.append(self.PARSE_CONFIG) + self.commands_async.append(self.CFG_FILES_DISTRO) + self.commands_async.append(self.CFG_FILES_MACH) + self.commands_async.append(self.CFG_FILES_SDKMACH) + self.commands_async.append(self.FILES_MATCH_CLASS) + self.run_next_command(self.LAYERS_REFRESH) + + def set_extra_inherit(self, bbclass): + inherits = self.server.runCommand(["getVariable", "INHERIT"]) or "" + inherits = inherits + " " + bbclass + self.server.runCommand(["setVariable", "INHERIT", inherits]) + + def set_bblayers(self, bblayers): + self.server.runCommand(["setVariable", "BBLAYERS", " ".join(bblayers)]) def set_machine(self, machine): self.server.runCommand(["setVariable", "MACHINE", machine]) @@ -232,62 +295,78 @@ class HobHandler(gobject.GObject): def set_sdk_machine(self, sdk_machine): self.server.runCommand(["setVariable", "SDKMACHINE", sdk_machine]) + def set_image_fstypes(self, image_fstypes): + self.server.runCommand(["setVariable", "IMAGE_FSTYPES", " ".join(image_fstypes).lstrip(" ")]) + def set_distro(self, distro): self.server.runCommand(["setVariable", "DISTRO", distro]) def set_package_format(self, format): - self.server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_%s" % format]) - - def reload_data(self, config=None): - img = self.model.selected_image - selected_packages, _ = self.model.get_selected_packages() - self.emit("reload-triggered", img, " ".join(selected_packages)) - self.current_command = self.REPARSE_FILES - self.run_next_command() + package_classes = "" + for pkgfmt in format.split(): + package_classes += ("package_%s" % pkgfmt + " ") + self.server.runCommand(["setVariable", "PACKAGE_CLASSES", package_classes]) def set_bbthreads(self, threads): self.server.runCommand(["setVariable", "BB_NUMBER_THREADS", threads]) def set_pmake(self, threads): pmake = "-j %s" % threads - self.server.runCommand(["setVariable", "BB_NUMBER_THREADS", pmake]) + self.server.runCommand(["setVariable", "PARALLEL_MAKE", pmake]) - def build_targets(self, tgts, configurator, build_type="image"): - self.build_type = build_type - targets = [] - nbbp = None - nbbf = None - targets.extend(tgts) - if self.build_toolchain and self.build_toolchain_headers: - targets.append("meta-toolchain-sdk") - elif self.build_toolchain: - targets.append("meta-toolchain") - self.build_queue = targets - - if not self.bbpath_ok: - if self.image_dir in self.bbpath.split(":"): - self.bbpath_ok = True - else: - nbbp = self.image_dir + def set_dl_dir(self, directory): + self.server.runCommand(["setVariable", "DL_DIR", directory]) + + def set_sstate_dir(self, directory): + self.server.runCommand(["setVariable", "SSTATE_DIR", directory]) - if not self.bbfiles_ok: - import re - pattern = "%s/\*.bb" % self.image_dir + def set_sstate_mirror(self, url): + self.server.runCommand(["setVariable", "SSTATE_MIRROR", url]) - for files in self.bbfiles.split(" "): - if re.match(pattern, files): - self.bbfiles_ok = True + def set_extra_size(self, image_extra_size): + self.server.runCommand(["setVariable", "IMAGE_ROOTFS_EXTRA_SPACE", str(image_extra_size)]) - if not self.bbfiles_ok: - nbbf = "%s/*.bb" % self.image_dir + def set_rootfs_size(self, image_rootfs_size): + self.server.runCommand(["setVariable", "IMAGE_ROOTFS_SIZE", str(image_rootfs_size)]) - if nbbp or nbbf: - configurator.insertTempBBPath(nbbp, nbbf) - self.bbpath_ok = True - self.bbfiles_ok = True + def set_incompatible_license(self, incompat_license): + self.server.runCommand(["setVariable", "INCOMPATIBLE_LICENSE", incompat_license]) - self.current_command = self.REPARSE_FILES - self.run_next_command() + def set_extra_config(self, extra_setting): + for key in extra_setting.keys(): + value = extra_setting[key] + self.server.runCommand(["setVariable", key, value]) + + def request_package_info_async(self): + self.commands_async.append(self.GENERATE_PACKAGEINFO) + self.run_next_command(self.POPULATE_PACKAGEINFO) + + def generate_recipes(self): + self.commands_async.append(self.PARSE_CONFIG) + self.commands_async.append(self.GENERATE_TGTS) + self.run_next_command(self.GENERATE_RECIPES) + + def generate_packages(self, tgts): + targets = [] + targets.extend(tgts) + self.recipe_queue = targets + self.commands_async.append(self.PARSE_CONFIG) + self.commands_async.append(self.PARSE_BBFILES) + self.commands_async.append(self.BUILD_TARGET_RECIPES) + self.run_next_command(self.GENERATE_PACKAGES) + + def generate_image(self, tgts, toolchain_build=False): + self.package_queue = tgts + self.toolchain_build = toolchain_build + self.commands_async.append(self.PARSE_CONFIG) + self.commands_async.append(self.PARSE_BBFILES) + self.commands_async.append(self.BUILD_TARGET_IMAGE) + self.run_next_command(self.GENERATE_IMAGE) + + def build_failed_async(self): + self.initcmd = None + self.commands_async = [] + self.building = False def cancel_build(self, force=False): if force: @@ -298,46 +377,83 @@ class HobHandler(gobject.GObject): # leave the workdir in a usable state self.server.runCommand(["stateShutdown"]) - def set_incompatible_license(self, incompatible): - self.server.runCommand(["setVariable", "INCOMPATIBLE_LICENSE", incompatible]) - - def toggle_toolchain(self, enabled): - if self.build_toolchain != enabled: - self.build_toolchain = enabled - - def toggle_toolchain_headers(self, enabled): - if self.build_toolchain_headers != enabled: - self.build_toolchain_headers = enabled - - def set_fstypes(self, fstypes): - self.server.runCommand(["setVariable", "IMAGE_FSTYPES", fstypes]) - - def add_image_output_type(self, output_type): - if output_type not in self.image_output_types: - self.image_output_types.append(output_type) - fstypes = " ".join(self.image_output_types).lstrip(" ") - self.set_fstypes(fstypes) - return self.image_output_types - - def remove_image_output_type(self, output_type): - if output_type in self.image_output_types: - ind = self.image_output_types.index(output_type) - self.image_output_types.pop(ind) - fstypes = " ".join(self.image_output_types).lstrip(" ") - self.set_fstypes(fstypes) - return self.image_output_types - - def get_image_deploy_dir(self): - return self.image_out_dir - - def make_temp_dir(self): - bb.utils.mkdirhier(self.image_dir) - - def remove_temp_dir(self): - bb.utils.remove(self.image_dir, True) - - def get_temp_recipe_path(self, name): - timestamp = datetime.date.today().isoformat() - image_file = "hob-%s-variant-%s.bb" % (name, timestamp) - recipepath = os.path.join(self.image_dir, image_file) - return recipepath + def reset_server(self): + self.server.runCommand(["resetCooker"]) + + def reset_build(self): + self.build.reset() + + def get_parameters(self): + # retrieve the parameters from bitbake + params = {} + params["core_base"] = self.server.runCommand(["getVariable", "COREBASE"]) or "" + hob_layer = params["core_base"] + "/meta-hob" + params["layer"] = (self.server.runCommand(["getVariable", "BBLAYERS"]) or "") + " " + hob_layer + params["dldir"] = self.server.runCommand(["getVariable", "DL_DIR"]) or "" + params["machine"] = self.server.runCommand(["getVariable", "MACHINE"]) or "" + params["distro"] = self.server.runCommand(["getVariable", "DISTRO"]) or "defaultsetup" + params["pclass"] = self.server.runCommand(["getVariable", "PACKAGE_CLASSES"]) or "" + params["sstatedir"] = self.server.runCommand(["getVariable", "SSTATE_DIR"]) or "" + params["sstatemirror"] = self.server.runCommand(["getVariable", "SSTATE_MIRROR"]) or "" + + num_threads = self.server.runCommand(["getCpuCount"]) + if not num_threads: + num_threads = 1 + max_threads = 65536 + else: + num_threads = int(num_threads) + max_threads = 16 * num_threads + params["max_threads"] = max_threads + + bbthread = self.server.runCommand(["getVariable", "BB_NUMBER_THREADS"]) + if not bbthread: + bbthread = num_threads + else: + bbthread = int(bbthread) + params["bbthread"] = bbthread + + pmake = self.server.runCommand(["getVariable", "PARALLEL_MAKE"]) + if not pmake: + pmake = num_threads + elif isinstance(pmake, int): + pass + else: + pmake = int(pmake.lstrip("-j ")) + params["pmake"] = pmake + + image_addr = self.server.runCommand(["getVariable", "DEPLOY_DIR_IMAGE"]) or "" + if self.server_addr: + image_addr = "http://" + self.server_addr + ":" + image_addr + params["image_addr"] = image_addr + + image_extra_size = self.server.runCommand(["getVariable", "IMAGE_ROOTFS_EXTRA_SPACE"]) + if not image_extra_size: + image_extra_size = 0 + else: + image_extra_size = int(image_extra_size) + params["image_extra_size"] = image_extra_size + + image_rootfs_size = self.server.runCommand(["getVariable", "IMAGE_ROOTFS_SIZE"]) + if not image_rootfs_size: + image_rootfs_size = 0 + else: + image_rootfs_size = int(image_rootfs_size) + params["image_rootfs_size"] = image_rootfs_size + + image_overhead_factor = self.server.runCommand(["getVariable", "IMAGE_OVERHEAD_FACTOR"]) + if not image_overhead_factor: + image_overhead_factor = 1 + else: + image_overhead_factor = float(image_overhead_factor) + params['image_overhead_factor'] = image_overhead_factor + + params["incompat_license"] = self.server.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) or "" + params["sdk_machine"] = self.server.runCommand(["getVariable", "SDKMACHINE"]) or self.server.runCommand(["getVariable", "SDK_ARCH"]) or "" + + #params["image_types"] = self.server.runCommand(["getVariable", "IMAGE_TYPES"]) or "" + params["image_fstypes"] = self.server.runCommand(["getVariable", "IMAGE_FSTYPES"]) or "" + """ + A workaround + """ + params["image_types"] = "jffs2 sum.jffs2 cramfs ext2 ext2.gz ext2.bz2 ext3 ext3.gz ext2.lzma btrfs live squashfs squashfs-lzma ubi tar tar.gz tar.bz2 tar.xz cpio cpio.gz cpio.xz cpio.lzma" + return params diff --git a/bitbake/lib/bb/ui/crumbs/hoblistmodel.py b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py new file mode 100644 index 0000000000..227ae4b5de --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py @@ -0,0 +1,765 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import gobject + +# +# PackageListModel +# +class PackageListModel(gtk.TreeStore): + """ + This class defines an gtk.TreeStore subclass which will convert the output + of the bb.event.TargetsTreeGenerated event into a gtk.TreeStore whilst also + providing convenience functions to access gtk.TreeModel subclasses which + provide filtered views of the data. + """ + (COL_NAME, COL_VER, COL_REV, COL_RNM, COL_SEC, COL_SUM, COL_RDEP, COL_RPROV, COL_SIZE, COL_BINB, COL_INC) = range(11) + + __gsignals__ = { + "packagelist-populated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + "package-selection-changed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + } + + def __init__(self): + + self.contents = None + self.images = None + self.pkgs_size = 0 + self.pn_path = {} + self.pkg_path = {} + + gtk.TreeStore.__init__ (self, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN) + + + """ + Find the model path for the item_name + Returns the path in the model or None + """ + def find_path_for_item(self, item_name): + if item_name not in self.pkg_path.keys(): + return None + else: + return self.pkg_path[item_name] + + def find_item_for_path(self, item_path): + return self[item_path][self.COL_NAME] + + """ + Helper function to determine whether an item is an item specified by filter + """ + def tree_model_filter(self, model, it, filter): + for key in filter.keys(): + if model.get_value(it, key) not in filter[key]: + return False + + return True + + """ + Create, if required, and return a filtered gtk.TreeModelSort + containing only the items specified by filter + """ + def tree_model(self, filter): + model = self.filter_new() + model.set_visible_func(self.tree_model_filter, filter) + + sort = gtk.TreeModelSort(model) + sort.set_sort_column_id(PackageListModel.COL_NAME, gtk.SORT_ASCENDING) + sort.set_default_sort_func(None) + return sort + + def convert_vpath_to_path(self, view_model, view_path): + # view_model is the model sorted + # get the path of the model filtered + filtered_model_path = view_model.convert_path_to_child_path(view_path) + # get the model filtered + filtered_model = view_model.get_model() + # get the path of the original model + path = filtered_model.convert_path_to_child_path(filtered_model_path) + return path + + def convert_path_to_vpath(self, view_model, path): + name = self.find_item_for_path(path) + it = view_model.get_iter_first() + while it: + child_it = view_model.iter_children(it) + while child_it: + view_name = view_model.get_value(child_it, self.COL_NAME) + if view_name == name: + view_path = view_model.get_path(child_it) + return view_path + child_it = view_model.iter_next(child_it) + it = view_model.iter_next(it) + return None + + """ + The populate() function takes as input the data from a + bb.event.PackageInfo event and populates the package list. + Once the population is done it emits gsignal packagelist-populated + to notify any listeners that the model is ready + """ + def populate(self, pkginfolist): + self.clear() + self.pkgs_size = 0 + self.pn_path = {} + self.pkg_path = {} + + for pkginfo in pkginfolist: + pn = pkginfo['PN'] + pv = pkginfo['PV'] + pr = pkginfo['PR'] + if pn in self.pn_path.keys(): + pniter = self.get_iter(self.pn_path[pn]) + else: + pniter = self.append(None) + self.set(pniter, self.COL_NAME, pn + '-' + pv + '-' + pr, + self.COL_INC, False) + self.pn_path[pn] = self.get_path(pniter) + + pkg = pkginfo['PKG'] + pkgv = pkginfo['PKGV'] + pkgr = pkginfo['PKGR'] + pkgsize = pkginfo['PKGSIZE_%s' % pkg] if 'PKGSIZE_%s' % pkg in pkginfo.keys() else "0" + pkg_rename = pkginfo['PKG_%s' % pkg] if 'PKG_%s' % pkg in pkginfo.keys() else "" + section = pkginfo['SECTION_%s' % pkg] if 'SECTION_%s' % pkg in pkginfo.keys() else "" + summary = pkginfo['SUMMARY_%s' % pkg] if 'SUMMARY_%s' % pkg in pkginfo.keys() else "" + rdep = pkginfo['RDEPENDS_%s' % pkg] if 'RDEPENDS_%s' % pkg in pkginfo.keys() else "" + rrec = pkginfo['RRECOMMENDS_%s' % pkg] if 'RRECOMMENDS_%s' % pkg in pkginfo.keys() else "" + rprov = pkginfo['RPROVIDES_%s' % pkg] if 'RPROVIDES_%s' % pkg in pkginfo.keys() else "" + + if 'ALLOW_EMPTY_%s' % pkg in pkginfo.keys(): + allow_empty = pkginfo['ALLOW_EMPTY_%s' % pkg] + elif 'ALLOW_EMPTY' in pkginfo.keys(): + allow_empty = pkginfo['ALLOW_EMPTY'] + else: + allow_empty = "" + + if pkgsize == "0" and not allow_empty: + continue + + if len(pkgsize) > 3: + size = '%.1f' % (int(pkgsize)*1.0/1024) + ' MB' + else: + size = pkgsize + ' KB' + + it = self.append(pniter) + self.pkg_path[pkg] = self.get_path(it) + self.set(it, self.COL_NAME, pkg, self.COL_VER, pkgv, + self.COL_REV, pkgr, self.COL_RNM, pkg_rename, + self.COL_SEC, section, self.COL_SUM, summary, + self.COL_RDEP, rdep + ' ' + rrec, + self.COL_RPROV, rprov, self.COL_SIZE, size, + self.COL_BINB, "", self.COL_INC, False) + + self.emit("packagelist-populated") + + """ + Check whether the item at item_path is included or not + """ + def path_included(self, item_path): + return self[item_path][self.COL_INC] + + """ + Update the model, send out the notification. + """ + def selection_change_notification(self): + self.emit("package-selection-changed") + + """ + Mark a certain package as selected. + All its dependencies are marked as selected. + The recipe provides the package is marked as selected. + If user explicitly selects a recipe, all its providing packages are selected + """ + def include_item(self, item_path, binb=""): + if self.path_included(item_path): + return + + item_name = self[item_path][self.COL_NAME] + item_rdep = self[item_path][self.COL_RDEP] + + self[item_path][self.COL_INC] = True + + self.selection_change_notification() + + it = self.get_iter(item_path) + + # If user explicitly selects a recipe, all its providing packages are selected. + if not self[item_path][self.COL_VER] and binb == "User Selected": + child_it = self.iter_children(it) + while child_it: + child_path = self.get_path(child_it) + child_included = self.path_included(child_path) + if not child_included: + self.include_item(child_path, binb="User Selected") + child_it = self.iter_next(child_it) + return + + # The recipe provides the package is also marked as selected + parent_it = self.iter_parent(it) + if parent_it: + parent_path = self.get_path(parent_it) + self[parent_path][self.COL_INC] = True + + item_bin = self[item_path][self.COL_BINB].split(', ') + if binb and not binb in item_bin: + item_bin.append(binb) + self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ') + + if item_rdep: + # Ensure all of the items deps are included and, where appropriate, + # add this item to their COL_BINB + for dep in item_rdep.split(" "): + if dep.startswith('('): + continue + # If the contents model doesn't already contain dep, add it + dep_path = self.find_path_for_item(dep) + if not dep_path: + continue + dep_included = self.path_included(dep_path) + + if dep_included and not dep in item_bin: + # don't set the COL_BINB to this item if the target is an + # item in our own COL_BINB + dep_bin = self[dep_path][self.COL_BINB].split(', ') + if not item_name in dep_bin: + dep_bin.append(item_name) + self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + elif not dep_included: + self.include_item(dep_path, binb=item_name) + + """ + Mark a certain package as de-selected. + All other packages that depends on this package are marked as de-selected. + If none of the packages provided by the recipe, the recipe should be marked as de-selected. + If user explicitly de-select a recipe, all its providing packages are de-selected. + """ + def exclude_item(self, item_path): + if not self.path_included(item_path): + return + + self[item_path][self.COL_INC] = False + + self.selection_change_notification() + + item_name = self[item_path][self.COL_NAME] + item_rdep = self[item_path][self.COL_RDEP] + it = self.get_iter(item_path) + + # If user explicitly de-select a recipe, all its providing packages are de-selected. + if not self[item_path][self.COL_VER]: + child_it = self.iter_children(it) + while child_it: + child_path = self.get_path(child_it) + child_included = self[child_path][self.COL_INC] + if child_included: + self.exclude_item(child_path) + child_it = self.iter_next(child_it) + return + + # If none of the packages provided by the recipe, the recipe should be marked as de-selected. + parent_it = self.iter_parent(it) + peer_iter = self.iter_children(parent_it) + enabled = 0 + while peer_iter: + peer_path = self.get_path(peer_iter) + if self[peer_path][self.COL_INC]: + enabled = 1 + break + peer_iter = self.iter_next(peer_iter) + if not enabled: + parent_path = self.get_path(parent_it) + self[parent_path][self.COL_INC] = False + + # All packages that depends on this package are de-selected. + if item_rdep: + for dep in item_rdep.split(" "): + if dep.startswith('('): + continue + dep_path = self.find_path_for_item(dep) + if not dep_path: + continue + dep_bin = self[dep_path][self.COL_BINB].split(', ') + if item_name in dep_bin: + dep_bin.remove(item_name) + self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + + item_bin = self[item_path][self.COL_BINB].split(', ') + if item_bin: + for binb in item_bin: + binb_path = self.find_path_for_item(binb) + if not binb_path: + continue + self.exclude_item(binb_path) + + """ + Package model may be incomplete, therefore when calling the + set_selected_packages(), some packages will not be set included. + Return the un-set packages list. + """ + def set_selected_packages(self, packagelist): + left = [] + for pn in packagelist: + if pn in self.pkg_path.keys(): + path = self.pkg_path[pn] + self.include_item(item_path=path, + binb="User Selected") + else: + left.append(pn) + + return left + + def get_selected_packages(self): + packagelist = [] + + it = self.get_iter_first() + while it: + child_it = self.iter_children(it) + while child_it: + if self.get_value(child_it, self.COL_INC): + name = self.get_value(child_it, self.COL_NAME) + packagelist.append(name) + child_it = self.iter_next(child_it) + it = self.iter_next(it) + + return packagelist + + """ + Return the selected package size, unit is KB. + """ + def get_packages_size(self): + packages_size = 0 + it = self.get_iter_first() + while it: + child_it = self.iter_children(it) + while child_it: + if self.get_value(child_it, self.COL_INC): + str_size = self.get_value(child_it, self.COL_SIZE) + if not str_size: + continue + + unit = str_size.split() + if unit[1] == 'MB': + size = float(unit[0])*1024 + else: + size = float(unit[0]) + packages_size += size + + child_it = self.iter_next(child_it) + it = self.iter_next(it) + return "%f" % packages_size + + """ + Empty self.contents by setting the include of each entry to None + """ + def reset(self): + self.pkgs_size = 0 + it = self.get_iter_first() + while it: + self.set(it, self.COL_INC, False) + child_it = self.iter_children(it) + while child_it: + self.set(child_it, + self.COL_INC, False, + self.COL_BINB, "") + child_it = self.iter_next(child_it) + it = self.iter_next(it) + + self.selection_change_notification() + +# +# RecipeListModel +# +class RecipeListModel(gtk.ListStore): + """ + This class defines an gtk.ListStore subclass which will convert the output + of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also + providing convenience functions to access gtk.TreeModel subclasses which + provide filtered views of the data. + """ + (COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC, COL_IMG, COL_INSTALL, COL_PN) = range(11) + + __dummy_image__ = "--select a base image--" + + __gsignals__ = { + "recipelist-populated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + "recipe-selection-changed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + } + + """ + """ + def __init__(self): + gtk.ListStore.__init__ (self, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN, + gobject.TYPE_BOOLEAN, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING) + + """ + Find the model path for the item_name + Returns the path in the model or None + """ + def find_path_for_item(self, item_name): + if self.non_target_name(item_name) or item_name not in self.pn_path.keys(): + return None + else: + return self.pn_path[item_name] + + def find_item_for_path(self, item_path): + return self[item_path][self.COL_NAME] + + """ + Helper method to determine whether name is a target pn + """ + def non_target_name(self, name): + if name and ('-native' in name): + return True + return False + + """ + Helper function to determine whether an item is an item specified by filter + """ + def tree_model_filter(self, model, it, filter): + name = model.get_value(it, self.COL_NAME) + if self.non_target_name(name): + return False + + for key in filter.keys(): + if model.get_value(it, key) not in filter[key]: + return False + + return True + + def sort_func(self, model, iter1, iter2): + val1 = model.get_value(iter1, RecipeListModel.COL_NAME) + val2 = model.get_value(iter2, RecipeListModel.COL_NAME) + return val1 > val2 + + """ + Create, if required, and return a filtered gtk.TreeModelSort + containing only the items which are items specified by filter + """ + def tree_model(self, filter): + model = self.filter_new() + model.set_visible_func(self.tree_model_filter, filter) + + sort = gtk.TreeModelSort(model) + sort.set_default_sort_func(self.sort_func) + return sort + + def convert_vpath_to_path(self, view_model, view_path): + filtered_model_path = view_model.convert_path_to_child_path(view_path) + filtered_model = view_model.get_model() + + # get the path of the original model + path = filtered_model.convert_path_to_child_path(filtered_model_path) + return path + + def convert_path_to_vpath(self, view_model, path): + it = view_model.get_iter_first() + while it: + name = self.find_item_for_path(path) + view_name = view_model.get_value(it, RecipeListModel.COL_NAME) + if view_name == name: + view_path = view_model.get_path(it) + return view_path + it = view_model.iter_next(it) + return None + + def map_runtime(self, event_model, runtime, rdep_type, name): + if rdep_type not in ['pkg', 'pn'] or runtime not in ['rdepends', 'rrecs']: + return + package_depends = event_model["%s-%s" % (runtime, rdep_type)].get(name, []) + pn_depends = [] + for package_depend in package_depends: + if 'task-' not in package_depend and package_depend in event_model["packages"].keys(): + pn_depends.append(event_model["packages"][package_depend]["pn"]) + else: + pn_depends.append(package_depend) + return list(set(pn_depends)) + + def subpkg_populate(self, event_model, pkg, desc, lic, group, atype, pn): + pn_depends = self.map_runtime(event_model, "rdepends", "pkg", pkg) + pn_depends += self.map_runtime(event_model, "rrecs", "pkg", pkg) + self.set(self.append(), self.COL_NAME, pkg, self.COL_DESC, desc, + self.COL_LIC, lic, self.COL_GROUP, group, + self.COL_DEPS, " ".join(pn_depends), self.COL_BINB, "", + self.COL_TYPE, atype, self.COL_INC, False, + self.COL_IMG, False, self.COL_INSTALL, "", self.COL_PN, pn) + + """ + The populate() function takes as input the data from a + bb.event.TargetsTreeGenerated event and populates the RecipeList. + Once the population is done it emits gsignal recipelist-populated + to notify any listeners that the model is ready + """ + def populate(self, event_model): + # First clear the model, in case repopulating + self.clear() + + # dummy image for prompt + self.set(self.append(), self.COL_NAME, self.__dummy_image__, + self.COL_DESC, "", + self.COL_LIC, "", self.COL_GROUP, "", + self.COL_DEPS, "", self.COL_BINB, "", + self.COL_TYPE, "image", self.COL_INC, False, + self.COL_IMG, False, self.COL_INSTALL, "", self.COL_PN, self.__dummy_image__) + + for item in event_model["pn"]: + name = item + desc = event_model["pn"][item]["description"] + lic = event_model["pn"][item]["license"] + group = event_model["pn"][item]["section"] + install = [] + + if ('task-' in name): + if ('lib32-' in name or 'lib64-' in name): + atype = 'mltask' + else: + atype = 'task' + for pkg in event_model["pn"][name]["packages"]: + self.subpkg_populate(event_model, pkg, desc, lic, group, atype, name) + continue + + elif ('-image-' in name): + atype = 'image' + depends = event_model["depends"].get(item, []) + rdepends = self.map_runtime(event_model, 'rdepends', 'pn', name) + depends = depends + rdepends + install = event_model["rdepends-pn"].get(item, []) + + elif ('meta-' in name): + atype = 'toolchain' + + elif (name == 'dummy-image' or name == 'dummy-toolchain'): + atype = 'dummy' + + else: + if ('lib32-' in name or 'lib64-' in name): + atype = 'mlrecipe' + else: + atype = 'recipe' + depends = event_model["depends"].get(item, []) + depends += self.map_runtime(event_model, 'rdepends', 'pn', item) + for pkg in event_model["pn"][name]["packages"]: + depends += self.map_runtime(event_model, 'rdepends', 'pkg', item) + depends += self.map_runtime(event_model, 'rrecs', 'pkg', item) + + self.set(self.append(), self.COL_NAME, item, self.COL_DESC, desc, + self.COL_LIC, lic, self.COL_GROUP, group, + self.COL_DEPS, " ".join(depends), self.COL_BINB, "", + self.COL_TYPE, atype, self.COL_INC, False, + self.COL_IMG, False, self.COL_INSTALL, " ".join(install), self.COL_PN, item) + + self.pn_path = {} + it = self.get_iter_first() + while it: + pn = self.get_value(it, self.COL_NAME) + path = self.get_path(it) + self.pn_path[pn] = path + it = self.iter_next(it) + + self.emit("recipelist-populated") + + """ + Update the model, send out the notification. + """ + def selection_change_notification(self): + self.emit("recipe-selection-changed") + + def path_included(self, item_path): + return self[item_path][self.COL_INC] + + """ + Append a certain image into the combobox + """ + def image_list_append(self, name, deps, install): + # check whether a certain image is there + if not name or self.find_path_for_item(name): + return + it = self.append() + self.set(it, self.COL_NAME, name, self.COL_DESC, "", + self.COL_LIC, "", self.COL_GROUP, "", + self.COL_DEPS, deps, self.COL_BINB, "", + self.COL_TYPE, "image", self.COL_INC, False, + self.COL_IMG, False, self.COL_INSTALL, install, + self.COL_PN, name) + self.pn_path[name] = self.get_path(it) + + """ + Add this item, and any of its dependencies, to the image contents + """ + def include_item(self, item_path, binb="", image_contents=False): + if self.path_included(item_path): + return + + item_name = self[item_path][self.COL_NAME] + item_deps = self[item_path][self.COL_DEPS] + + self[item_path][self.COL_INC] = True + self.selection_change_notification() + + item_bin = self[item_path][self.COL_BINB].split(', ') + if binb and not binb in item_bin: + item_bin.append(binb) + self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ') + + # We want to do some magic with things which are brought in by the + # base image so tag them as so + if image_contents: + self[item_path][self.COL_IMG] = True + + if item_deps: + # Ensure all of the items deps are included and, where appropriate, + # add this item to their COL_BINB + for dep in item_deps.split(" "): + # If the contents model doesn't already contain dep, add it + dep_path = self.find_path_for_item(dep) + if not dep_path: + continue + dep_included = self.path_included(dep_path) + + if dep_included and not dep in item_bin: + # don't set the COL_BINB to this item if the target is an + # item in our own COL_BINB + dep_bin = self[dep_path][self.COL_BINB].split(', ') + if not item_name in dep_bin: + dep_bin.append(item_name) + self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + elif not dep_included: + self.include_item(dep_path, binb=item_name, image_contents=image_contents) + + def exclude_item(self, item_path): + if not self.path_included(item_path): + return + + self[item_path][self.COL_INC] = False + + self.selection_change_notification() + + item_name = self[item_path][self.COL_NAME] + item_deps = self[item_path][self.COL_DEPS] + if item_deps: + for dep in item_deps.split(" "): + dep_path = self.find_path_for_item(dep) + if not dep_path: + continue + dep_bin = self[dep_path][self.COL_BINB].split(', ') + if item_name in dep_bin: + dep_bin.remove(item_name) + self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + + item_bin = self[item_path][self.COL_BINB].split(', ') + if item_bin: + for binb in item_bin: + binb_path = self.find_path_for_item(binb) + if not binb_path: + continue + self.exclude_item(binb_path) + + def reset(self): + it = self.get_iter_first() + while it: + self.set(it, + self.COL_INC, False, + self.COL_BINB, "", + self.COL_IMG, False) + it = self.iter_next(it) + + self.selection_change_notification() + + """ + Returns two lists. One of user selected recipes and the other containing + all selected recipes + """ + def get_selected_recipes(self): + allrecipes = [] + userrecipes = [] + + it = self.get_iter_first() + while it: + if self.get_value(it, self.COL_INC): + name = self.get_value(it, self.COL_PN) + type = self.get_value(it, self.COL_TYPE) + if type != "image": + allrecipes.append(name) + sel = "User Selected" in self.get_value(it, self.COL_BINB) + if sel: + userrecipes.append(name) + it = self.iter_next(it) + + return list(set(userrecipes)), list(set(allrecipes)) + + def set_selected_recipes(self, recipelist): + for pn in recipelist: + if pn in self.pn_path.keys(): + path = self.pn_path[pn] + self.include_item(item_path=path, + binb="User Selected") + + def get_selected_image(self): + it = self.get_iter_first() + while it: + if self.get_value(it, self.COL_INC): + name = self.get_value(it, self.COL_PN) + type = self.get_value(it, self.COL_TYPE) + if type == "image": + sel = "User Selected" in self.get_value(it, self.COL_BINB) + if sel: + return name + it = self.iter_next(it) + return None + + def set_selected_image(self, img): + if img == None: + return + path = self.find_path_for_item(img) + self.include_item(item_path=path, + binb="User Selected", + image_contents=True) diff --git a/bitbake/lib/bb/ui/crumbs/hobpages.py b/bitbake/lib/bb/ui/crumbs/hobpages.py new file mode 100755 index 0000000000..be76d0db27 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hobpages.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.hobwidget import hwc + +# +# HobPage: the super class for all Hob-related pages +# +class HobPage (gtk.VBox): + + def __init__(self, builder, title = None): + super(HobPage, self).__init__(False, 0) + self.builder = builder + self.builder_width, self.builder_height = self.builder.size_request() + + if title == None: + self.title = "HOB -- Image Creator" + else: + self.title = title + + self.box_group_area = gtk.VBox(False, 15) + self.box_group_area.set_size_request(self.builder_width - 73 - 73, self.builder_height - 88 - 15 - 15) + self.group_align = gtk.Alignment(xalign = 0, yalign=0.5, xscale=1, yscale=1) + self.group_align.set_padding(15, 15, 73, 73) + self.group_align.add(self.box_group_area) + self.box_group_area.set_homogeneous(False) + + + def add_onto_top_bar(self, widget = None, padding = 0): + # the top button occupies 1/7 of the page height + # setup an event box + eventbox = gtk.EventBox() + style = eventbox.get_style().copy() + style.bg[gtk.STATE_NORMAL] = eventbox.get_colormap().alloc_color(HobColors.LIGHT_GRAY, False, False) + eventbox.set_style(style) + eventbox.set_size_request(-1, 88) + + hbox = gtk.HBox() + + label = gtk.Label() + label.set_markup("%s" % self.title) + hbox.pack_start(label, expand=False, fill=False, padding=20) + + if widget != None: + # add the widget in the event box + hbox.pack_end(widget, expand=False, fill=False, padding=padding) + eventbox.add(hbox) + + return eventbox + + def span_tag(self, px="12px", weight="normal", forground="#1c1c1c"): + span_tag = "weight=\'%s\' foreground=\'%s\' font_desc=\'%s\'" % (weight, forground, px) + return span_tag + + def append_toolbar_button(self, toolbar, buttonname, icon_disp, icon_hovor, tip, cb): + # Create a button and append it on the toolbar according to button name + icon = gtk.Image() + icon_display = icon_disp + icon_hover = icon_hovor + pix_buffer = gtk.gdk.pixbuf_new_from_file(icon_display) + icon.set_from_pixbuf(pix_buffer) + tip_text = tip + button = toolbar.append_element(gtk.TOOLBAR_CHILD_RADIOBUTTON, None, + buttonname, tip_text, "Private text", icon, + cb, None) + return toolbar, button diff --git a/bitbake/lib/bb/ui/crumbs/hobprefs.py b/bitbake/lib/bb/ui/crumbs/hobprefs.py deleted file mode 100644 index 3f6f128808..0000000000 --- a/bitbake/lib/bb/ui/crumbs/hobprefs.py +++ /dev/null @@ -1,335 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011 Intel Corporation -# -# Authored by Joshua Lock -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import gtk -import glib -from bb.ui.crumbs.configurator import Configurator - -class HobPrefs(gtk.Dialog): - """ - """ - def empty_combo_text(self, combo_text): - model = combo_text.get_model() - if model: - model.clear() - - def output_type_toggled_cb(self, check, handler): - ot = check.get_label() - enabled = check.get_active() - if enabled: - self.selected_image_types = handler.add_image_output_type(ot) - else: - self.selected_image_types = handler.remove_image_output_type(ot) - - self.configurator.setConfVar('IMAGE_FSTYPES', "%s" % " ".join(self.selected_image_types).lstrip(" ")) - self.reload_required = True - - def sdk_machine_combo_changed_cb(self, combo, handler): - sdk_mach = combo.get_active_text() - if sdk_mach != self.curr_sdk_mach: - self.curr_sdk_mach = sdk_mach - self.configurator.setConfVar('SDKMACHINE', sdk_mach) - handler.set_sdk_machine(sdk_mach) - - def update_sdk_machines(self, handler, sdk_machines): - active = 0 - # disconnect the signal handler before updating the combo model - if self.sdk_machine_handler_id: - self.sdk_machine_combo.disconnect(self.sdk_machine_handler_id) - self.sdk_machine_handler_id = None - - self.empty_combo_text(self.sdk_machine_combo) - for sdk_machine in sdk_machines: - self.sdk_machine_combo.append_text(sdk_machine) - if sdk_machine == self.curr_sdk_mach: - self.sdk_machine_combo.set_active(active) - active = active + 1 - - self.sdk_machine_handler_id = self.sdk_machine_combo.connect("changed", self.sdk_machine_combo_changed_cb, handler) - - def distro_combo_changed_cb(self, combo, handler): - distro = combo.get_active_text() - if distro != self.curr_distro: - self.curr_distro = distro - self.configurator.setConfVar('DISTRO', distro) - handler.set_distro(distro) - self.reload_required = True - - def update_distros(self, handler, distros): - active = 0 - # disconnect the signal handler before updating combo model - if self.distro_handler_id: - self.distro_combo.disconnect(self.distro_handler_id) - self.distro_handler_id = None - - self.empty_combo_text(self.distro_combo) - for distro in distros: - self.distro_combo.append_text(distro) - if distro == self.curr_distro: - self.distro_combo.set_active(active) - active = active + 1 - - self.distro_handler_id = self.distro_combo.connect("changed", self.distro_combo_changed_cb, handler) - - def package_format_combo_changed_cb(self, combo, handler): - package_format = combo.get_active_text() - if package_format != self.curr_package_format: - self.curr_package_format = package_format - self.configurator.setConfVar('PACKAGE_CLASSES', 'package_%s' % package_format) - handler.set_package_format(package_format) - self.reload_required = True - - def update_package_formats(self, handler, formats): - active = 0 - # disconnect the signal handler before updating the model - if self.package_handler_id: - self.package_combo.disconnect(self.package_handler_id) - self.package_handler_id = None - - self.empty_combo_text(self.package_combo) - for format in formats: - self.package_combo.append_text(format) - if format == self.curr_package_format: - self.package_combo.set_active(active) - active = active + 1 - - self.package_handler_id = self.package_combo.connect("changed", self.package_format_combo_changed_cb, handler) - - def include_gplv3_cb(self, toggle): - excluded = toggle.get_active() - orig_incompatible = self.configurator.getConfVar('INCOMPATIBLE_LICENSE') - new_incompatible = "" - if excluded: - if not orig_incompatible: - new_incompatible = "GPLv3" - elif not orig_incompatible.find('GPLv3'): - new_incompatible = "%s GPLv3" % orig_incompatible - else: - new_incompatible = orig_incompatible.replace('GPLv3', '') - - if new_incompatible != orig_incompatible: - self.handler.set_incompatible_license(new_incompatible) - self.configurator.setConfVar('INCOMPATIBLE_LICENSE', new_incompatible) - self.reload_required = True - - def change_bb_threads_cb(self, spinner): - val = spinner.get_value_as_int() - self.handler.set_bbthreads(val) - self.configurator.setConfVar('BB_NUMBER_THREADS', val) - - def change_make_threads_cb(self, spinner): - val = spinner.get_value_as_int() - self.handler.set_pmake(val) - self.configurator.setConfVar('PARALLEL_MAKE', "-j %s" % val) - - def toggle_toolchain_cb(self, check): - enabled = check.get_active() - toolchain = '0' - if enabled: - toolchain = '1' - self.handler.toggle_toolchain(enabled) - self.configurator.setConfVar('HOB_BUILD_TOOLCHAIN', toolchain) - - def toggle_headers_cb(self, check): - enabled = check.get_active() - headers = '0' - if enabled: - headers = '1' - self.handler.toggle_toolchain_headers(enabled) - self.configurator.setConfVar('HOB_BUILD_TOOLCHAIN_HEADERS', headers) - - def set_parent_window(self, parent): - self.set_transient_for(parent) - - def write_changes(self): - self.configurator.writeConf() - - def prefs_response_cb(self, dialog, response): - if self.reload_required: - glib.idle_add(self.handler.reload_data) - self.reload_required = False - - def __init__(self, configurator, handler, curr_sdk_mach, curr_distro, pclass, - pmake, bbthread, selected_image_types, all_image_types, - gplv3disabled, build_toolchain, build_toolchain_headers): - """ - """ - gtk.Dialog.__init__(self, "Preferences", None, - gtk.DIALOG_DESTROY_WITH_PARENT, - (gtk.STOCK_CLOSE, gtk.RESPONSE_OK)) - - self.set_border_width(6) - self.vbox.set_property("spacing", 12) - self.action_area.set_property("spacing", 12) - self.action_area.set_property("border-width", 6) - - self.handler = handler - self.configurator = configurator - - self.curr_sdk_mach = curr_sdk_mach - self.curr_distro = curr_distro - self.curr_package_format = pclass - self.pmake = pmake - self.bbthread = bbthread - self.selected_image_types = selected_image_types.split(" ") - self.gplv3disabled = gplv3disabled - self.build_toolchain = build_toolchain - self.build_toolchain_headers = build_toolchain_headers - - self.reload_required = False - self.distro_handler_id = None - self.sdk_machine_handler_id = None - self.package_handler_id = None - - left = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) - right = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) - - label = gtk.Label() - label.set_markup("Policy") - label.show() - frame = gtk.Frame() - frame.set_label_widget(label) - frame.set_shadow_type(gtk.SHADOW_NONE) - frame.show() - self.vbox.pack_start(frame) - pbox = gtk.VBox(False, 12) - pbox.show() - frame.add(pbox) - hbox = gtk.HBox(False, 12) - hbox.show() - pbox.pack_start(hbox, expand=False, fill=False, padding=6) - # Distro selector - label = gtk.Label("Distribution:") - label.show() - hbox.pack_start(label, expand=False, fill=False, padding=6) - self.distro_combo = gtk.combo_box_new_text() - self.distro_combo.set_tooltip_text("Select the Yocto distribution you would like to use") - self.distro_combo.show() - hbox.pack_start(self.distro_combo, expand=False, fill=False, padding=6) - # Exclude GPLv3 - check = gtk.CheckButton("Exclude GPLv3 packages") - check.set_tooltip_text("Check this box to prevent GPLv3 packages from being included in your image") - check.show() - check.set_active(self.gplv3disabled) - check.connect("toggled", self.include_gplv3_cb) - hbox.pack_start(check, expand=False, fill=False, padding=6) - hbox = gtk.HBox(False, 12) - hbox.show() - pbox.pack_start(hbox, expand=False, fill=False, padding=6) - # Package format selector - label = gtk.Label("Package format:") - label.show() - hbox.pack_start(label, expand=False, fill=False, padding=6) - self.package_combo = gtk.combo_box_new_text() - self.package_combo.set_tooltip_text("""The package format is that used in creation - of the root filesystem and also dictates the package manager used in your image""") - self.package_combo.show() - hbox.pack_start(self.package_combo, expand=False, fill=False, padding=6) - if all_image_types: - # Image output type selector - label = gtk.Label("Image output types:") - label.show() - hbox.pack_start(label, expand=False, fill=False, padding=6) - chk_cnt = 3 - for it in all_image_types.split(" "): - chk_cnt = chk_cnt + 1 - if chk_cnt % 6 == 0: - hbox = gtk.HBox(False, 12) - hbox.show() - pbox.pack_start(hbox, expand=False, fill=False, padding=6) - chk = gtk.CheckButton(it) - if it in self.selected_image_types: - chk.set_active(True) - chk.set_tooltip_text("Build an %s image" % it) - chk.connect("toggled", self.output_type_toggled_cb, handler) - chk.show() - hbox.pack_start(chk, expand=False, fill=False, padding=3) - # BitBake - label = gtk.Label() - label.set_markup("BitBake") - label.show() - frame = gtk.Frame() - frame.set_label_widget(label) - frame.set_shadow_type(gtk.SHADOW_NONE) - frame.show() - self.vbox.pack_start(frame) - pbox = gtk.VBox(False, 12) - pbox.show() - frame.add(pbox) - hbox = gtk.HBox(False, 12) - hbox.show() - pbox.pack_start(hbox, expand=False, fill=False, padding=6) - label = gtk.Label("BitBake threads:") - label.show() - # NOTE: may be a good idea in future to intelligently cap the maximum - # values but we need more data to make an educated decision, for now - # set a high maximum as a value for upper bounds is required by the - # gtk.Adjustment - spin_max = 30 # seems like a high enough arbitrary number - hbox.pack_start(label, expand=False, fill=False, padding=6) - bbadj = gtk.Adjustment(value=self.bbthread, lower=1, upper=spin_max, step_incr=1) - bbspinner = gtk.SpinButton(adjustment=bbadj, climb_rate=1, digits=0) - bbspinner.show() - bbspinner.connect("value-changed", self.change_bb_threads_cb) - hbox.pack_start(bbspinner, expand=False, fill=False, padding=6) - label = gtk.Label("Make threads:") - label.show() - hbox.pack_start(label, expand=False, fill=False, padding=6) - madj = gtk.Adjustment(value=self.pmake, lower=1, upper=spin_max, step_incr=1) - makespinner = gtk.SpinButton(adjustment=madj, climb_rate=1, digits=0) - makespinner.connect("value-changed", self.change_make_threads_cb) - makespinner.show() - hbox.pack_start(makespinner, expand=False, fill=False, padding=6) - # Toolchain - label = gtk.Label() - label.set_markup("External Toolchain") - label.show() - frame = gtk.Frame() - frame.set_label_widget(label) - frame.set_shadow_type(gtk.SHADOW_NONE) - frame.show() - self.vbox.pack_start(frame) - pbox = gtk.VBox(False, 12) - pbox.show() - frame.add(pbox) - hbox = gtk.HBox(False, 12) - hbox.show() - pbox.pack_start(hbox, expand=False, fill=False, padding=6) - toolcheck = gtk.CheckButton("Build external development toolchain with image") - toolcheck.show() - toolcheck.set_active(self.build_toolchain) - toolcheck.connect("toggled", self.toggle_toolchain_cb) - hbox.pack_start(toolcheck, expand=False, fill=False, padding=6) - hbox = gtk.HBox(False, 12) - hbox.show() - pbox.pack_start(hbox, expand=False, fill=False, padding=6) - label = gtk.Label("Toolchain host:") - label.show() - hbox.pack_start(label, expand=False, fill=False, padding=6) - self.sdk_machine_combo = gtk.combo_box_new_text() - self.sdk_machine_combo.set_tooltip_text("Select the host architecture of the external machine") - self.sdk_machine_combo.show() - hbox.pack_start(self.sdk_machine_combo, expand=False, fill=False, padding=6) - # headerscheck = gtk.CheckButton("Include development headers with toolchain") - # headerscheck.show() - # headerscheck.set_active(self.build_toolchain_headers) - # headerscheck.connect("toggled", self.toggle_headers_cb) - # hbox.pack_start(headerscheck, expand=False, fill=False, padding=6) - self.connect("response", self.prefs_response_cb) diff --git a/bitbake/lib/bb/ui/crumbs/hobwidget.py b/bitbake/lib/bb/ui/crumbs/hobwidget.py new file mode 100644 index 0000000000..890151ddd4 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hobwidget.py @@ -0,0 +1,805 @@ +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011-2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import gobject +import os +import os.path +from bb.ui.crumbs.hobcolor import HobColors + +class hwc: + + MAIN_WIN_WIDTH = 1024 + MAIN_WIN_HEIGHT = 700 + +class hic: + + HOB_ICON_BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ("ui/icons/")) + + ICON_RCIPE_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_display.png')) + ICON_RCIPE_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('recipe/recipe_hover.png')) + ICON_PACKAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_display.png')) + ICON_PACKAGES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('packages/packages_hover.png')) + ICON_LAYERS_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_display.png')) + ICON_LAYERS_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('layers/layers_hover.png')) + ICON_TEMPLATES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('templates/templates_display.png')) + ICON_TEMPLATES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('templates/templates_hover.png')) + ICON_IMAGES_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('images/images_display.png')) + ICON_IMAGES_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('images/images_hover.png')) + ICON_SETTINGS_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_display.png')) + ICON_SETTINGS_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('settings/settings_hover.png')) + ICON_INFO_DISPLAY_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_display.png')) + ICON_INFO_HOVER_FILE = os.path.join(HOB_ICON_BASE_DIR, ('info/info_hover.png')) + ICON_INDI_CONFIRM_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/confirmation.png')) + ICON_INDI_ERROR_FILE = os.path.join(HOB_ICON_BASE_DIR, ('indicators/error.png')) + +class HobWidget: + @classmethod + def resize_widget(cls, screen, widget, widget_width, widget_height): + screen_width, screen_height = screen.get_size_request() + ratio_height = screen_width * hwc.MAIN_WIN_HEIGHT/hwc.MAIN_WIN_WIDTH + if ratio_height < screen_height: + screen_height = ratio_height + widget_width = widget_width * screen_width/hwc.MAIN_WIN_WIDTH + widget_height = widget_height * screen_height/hwc.MAIN_WIN_HEIGHT + widget.set_size_request(widget_width, widget_height) + + @classmethod + def _toggle_cb(cls, cell, path, model, column): + it = model.get_iter(path) + val = model.get_value(it, column) + val = not val + model.set(it, column, val) + + @classmethod + def _pkgfmt_up_clicked_cb(cls, button, tree_selection): + (model, it) = tree_selection.get_selected() + if not it: + return + path = model.get_path(it) + if path[0] <= 0: + return + + pre_it = model.get_iter_first() + if not pre_it: + return + else: + while model.iter_next(pre_it) : + if model.get_value(model.iter_next(pre_it), 1) != model.get_value(it, 1): + pre_it = model.iter_next(pre_it) + else: + break + + cur_index = model.get_value(it, 0) + pre_index = cur_index + if pre_it: + model.set(pre_it, 0, pre_index) + cur_index = cur_index - 1 + model.set(it, 0, cur_index) + + @classmethod + def _pkgfmt_down_clicked_cb(cls, button, tree_selection): + (model, it) = tree_selection.get_selected() + if not it: + return + next_it = model.iter_next(it) + if not next_it: + return + cur_index = model.get_value(it, 0) + next_index = cur_index + model.set(next_it, 0, next_index) + cur_index = cur_index + 1 + model.set(it, 0, cur_index) + + @classmethod + def _tree_selection_changed_cb(cls, tree_selection, button1, button2): + (model, it) = tree_selection.get_selected() + inc = model.get_value(it, 2) + if inc: + button1.set_sensitive(True) + button2.set_sensitive(True) + else: + button1.set_sensitive(False) + button2.set_sensitive(False) + + @classmethod + def _sort_func(cls, model, iter1, iter2, data): + val1 = model.get_value(iter1, 0) + val2 = model.get_value(iter2, 0) + inc1 = model.get_value(iter1, 2) + inc2 = model.get_value(iter2, 2) + if inc1 != inc2: + return inc2 - inc1 + else: + return val1 - val2 + + @classmethod + def gen_pkgfmt_widget(cls, curr_package_format, all_package_format, tooltip=""): + pkgfmt_hbox = gtk.HBox(False, 15) + + pkgfmt_store = gtk.ListStore(int, str, gobject.TYPE_BOOLEAN) + for format in curr_package_format.split(): + pkgfmt_store.set(pkgfmt_store.append(), 1, format, 2, True) + for format in all_package_format: + if format not in curr_package_format: + pkgfmt_store.set(pkgfmt_store.append(), 1, format, 2, False) + pkgfmt_tree = gtk.TreeView(pkgfmt_store) + pkgfmt_tree.set_headers_clickable(True) + pkgfmt_tree.set_headers_visible(False) + tree_selection = pkgfmt_tree.get_selection() + tree_selection.set_mode(gtk.SELECTION_SINGLE) + + col = gtk.TreeViewColumn('NO') + col.set_sort_column_id(0) + col.set_sort_order(gtk.SORT_ASCENDING) + col.set_clickable(False) + col1 = gtk.TreeViewColumn('TYPE') + col1.set_min_width(130) + col1.set_max_width(140) + col2 = gtk.TreeViewColumn('INCLUDED') + col2.set_min_width(60) + col2.set_max_width(70) + pkgfmt_tree.append_column(col1) + pkgfmt_tree.append_column(col2) + cell = gtk.CellRendererText() + cell1 = gtk.CellRendererText() + cell1.set_property('width-chars', 10) + cell2 = gtk.CellRendererToggle() + cell2.set_property('activatable', True) + cell2.connect("toggled", cls._toggle_cb, pkgfmt_store, 2) + col.pack_start(cell, True) + col1.pack_start(cell1, True) + col2.pack_end(cell2, True) + col.set_attributes(cell, text=0) + col1.set_attributes(cell1, text=1) + col2.set_attributes(cell2, active=2) + + pkgfmt_store.set_sort_func(0, cls._sort_func, None) + pkgfmt_store.set_sort_column_id(0, gtk.SORT_ASCENDING) + + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scroll.set_shadow_type(gtk.SHADOW_IN) + scroll.add(pkgfmt_tree) + scroll.set_size_request(200,60) + pkgfmt_hbox.pack_start(scroll, False, False, 0) + + vbox = gtk.VBox(False, 5) + pkgfmt_hbox.pack_start(vbox, False, False, 15) + + up = gtk.Button() + image = gtk.Image() + image.set_from_stock(gtk.STOCK_GO_UP, gtk.ICON_SIZE_MENU) + up.set_image(image) + up.set_size_request(50,30) + up.connect("clicked", cls._pkgfmt_up_clicked_cb, tree_selection) + vbox.pack_start(up, False, False, 5) + + down = gtk.Button() + image = gtk.Image() + image.set_from_stock(gtk.STOCK_GO_DOWN, gtk.ICON_SIZE_MENU) + down.set_image(image) + down.set_size_request(50,30) + down.connect("clicked", cls._pkgfmt_down_clicked_cb, tree_selection) + vbox.pack_start(down, False, False, 5) + tree_selection.connect("changed", cls._tree_selection_changed_cb, up, down) + + image = gtk.Image() + image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON) + image.set_tooltip_text(tooltip) + pkgfmt_hbox.pack_start(image, expand=False, fill=False) + + pkgfmt_hbox.show_all() + + return pkgfmt_hbox, pkgfmt_store + + @classmethod + def gen_combo_widget(cls, curr_item, all_item, tooltip=""): + hbox = gtk.HBox(False, 10) + combo = gtk.combo_box_new_text() + hbox.pack_start(combo, expand=False, fill=False) + + index = 0 + for item in all_item or []: + combo.append_text(item) + if item == curr_item: + combo.set_active(index) + index += 1 + + image = gtk.Image() + image.show() + image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON) + image.set_tooltip_text(tooltip) + + hbox.pack_start(image, expand=False, fill=False) + + hbox.show_all() + + return hbox, combo + + @classmethod + def gen_label_widget(cls, content): + label = gtk.Label() + label.set_alignment(0, 0) + label.set_markup(content) + label.show() + return label + + @classmethod + def _select_path_cb(cls, action, parent, entry): + dialog = gtk.FileChooserDialog("", parent, + gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, + (gtk.STOCK_OK, gtk.RESPONSE_YES, + gtk.STOCK_CANCEL, gtk.RESPONSE_NO)) + response = dialog.run() + if response == gtk.RESPONSE_YES: + path = dialog.get_filename() + entry.set_text(path) + + dialog.destroy() + + @classmethod + def gen_entry_widget(cls, split_model, content, parent, tooltip=""): + hbox = gtk.HBox(False, 10) + entry = gtk.Entry() + entry.set_text(content) + + if split_model: + hbox.pack_start(entry, expand=True, fill=True) + else: + table = gtk.Table(1, 10, True) + hbox.pack_start(table, expand=True, fill=True) + table.attach(entry, 0, 9, 0, 1) + image = gtk.Image() + image.set_from_stock(gtk.STOCK_OPEN,gtk.ICON_SIZE_BUTTON) + open_button = gtk.Button() + open_button.set_image(image) + open_button.connect("clicked", cls._select_path_cb, parent, entry) + table.attach(open_button, 9, 10, 0, 1) + + image = gtk.Image() + image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON) + image.set_tooltip_text(tooltip) + hbox.pack_start(image, expand=False, fill=False) + + hbox.show_all() + + return hbox, entry + + @classmethod + def gen_spinner_widget(cls, content, lower, upper, tooltip=""): + hbox = gtk.HBox(False, 10) + adjust = gtk.Adjustment(value=content, lower=lower, upper=upper, step_incr=1) + spinner = gtk.SpinButton(adjustment=adjust, climb_rate=1, digits=0) + + spinner.set_value(content) + hbox.pack_start(spinner, expand=False, fill=False) + + image = gtk.Image() + image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON) + image.set_tooltip_text(tooltip) + hbox.pack_start(image, expand=False, fill=False) + + hbox.show_all() + + return hbox, spinner + + @classmethod + def conf_error(cls, parent, lbl): + dialog = CrumbsDialog(parent, lbl) + dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + response = dialog.run() + dialog.destroy() + + @classmethod + def _add_layer_cb(cls, action, layer_store, parent): + dialog = gtk.FileChooserDialog("Add new layer", parent, + gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, + (gtk.STOCK_OK, gtk.RESPONSE_YES, + gtk.STOCK_CANCEL, gtk.RESPONSE_NO)) + label = gtk.Label("Select the layer you wish to add") + label.show() + dialog.set_extra_widget(label) + response = dialog.run() + path = dialog.get_filename() + dialog.destroy() + + lbl = "Error\nUnable to load layer %s because " % path + if response == gtk.RESPONSE_YES: + import os + import os.path + layers = [] + it = layer_store.get_iter_first() + while it: + layers.append(layer_store.get_value(it, 0)) + it = layer_store.iter_next(it) + + if not path: + lbl += "it is an invalid path." + elif not os.path.exists(path+"/conf/layer.conf"): + lbl += "there is no layer.conf inside the directory." + elif path in layers: + lbl += "it is already in loaded layers." + else: + layer_store.append([path]) + return + cls.conf_error(parent, lbl) + + @classmethod + def _del_layer_cb(cls, action, tree_selection, layer_store): + model, iter = tree_selection.get_selected() + if iter: + layer_store.remove(iter) + + @classmethod + def _toggle_layer_cb(cls, cell, path, layer_store): + name = layer_store[path][0] + toggle = not layer_store[path][1] + layer_store[path][1] = toggle + + @classmethod + def gen_layer_widget(cls, split_model, layers, layers_avail, window, tooltip=""): + hbox = gtk.HBox(False, 10) + + layer_tv = gtk.TreeView() + layer_tv.set_rules_hint(True) + layer_tv.set_headers_visible(False) + tree_selection = layer_tv.get_selection() + tree_selection.set_mode(gtk.SELECTION_SINGLE) + + col0= gtk.TreeViewColumn('Path') + cell0 = gtk.CellRendererText() + cell0.set_padding(5,2) + col0.pack_start(cell0, True) + col0.set_attributes(cell0, text=0) + layer_tv.append_column(col0) + + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scroll.set_shadow_type(gtk.SHADOW_IN) + scroll.add(layer_tv) + + table_layer = gtk.Table(2, 10, False) + hbox.pack_start(table_layer, expand=True, fill=True) + + if split_model: + table_layer.attach(scroll, 0, 10, 0, 2) + + layer_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN) + for layer in layers: + layer_store.set(layer_store.append(), 0, layer, 1, True) + for layer in layers_avail: + if layer not in layers: + layer_store.set(layer_store.append(), 0, layer, 1, False) + + col1 = gtk.TreeViewColumn('Included') + layer_tv.append_column(col1) + + cell1 = gtk.CellRendererToggle() + cell1.connect("toggled", cls._toggle_layer_cb, layer_store) + col1.pack_start(cell1, True) + col1.set_attributes(cell1, active=1) + + else: + table_layer.attach(scroll, 0, 10, 0, 1) + + layer_store = gtk.ListStore(gobject.TYPE_STRING) + for layer in layers: + layer_store.set(layer_store.append(), 0, layer) + + image = gtk.Image() + image.set_from_stock(gtk.STOCK_ADD,gtk.ICON_SIZE_MENU) + add_button = gtk.Button() + add_button.set_image(image) + add_button.connect("clicked", cls._add_layer_cb, layer_store, window) + table_layer.attach(add_button, 0, 5, 1, 2, gtk.EXPAND | gtk.FILL, 0, 0, 0) + image = gtk.Image() + image.set_from_stock(gtk.STOCK_REMOVE,gtk.ICON_SIZE_MENU) + del_button = gtk.Button() + del_button.set_image(image) + del_button.connect("clicked", cls._del_layer_cb, tree_selection, layer_store) + table_layer.attach(del_button, 5, 10, 1, 2, gtk.EXPAND | gtk.FILL, 0, 0, 0) + layer_tv.set_model(layer_store) + + hbox.show_all() + + return hbox, layer_store + + @classmethod + def _toggle_single_cb(cls, cell, select_path, treeview, toggle_column): + model = treeview.get_model() + if not model: + return + iter = model.get_iter_first() + while iter: + path = model.get_path(iter) + model[path][toggle_column] = False + iter = model.iter_next(iter) + + model[select_path][toggle_column] = True + + @classmethod + def gen_imgtv_widget(cls, col0_width, col1_width): + vbox = gtk.VBox(False, 10) + + imgsel_tv = gtk.TreeView() + imgsel_tv.set_rules_hint(True) + imgsel_tv.set_headers_visible(False) + tree_selection = imgsel_tv.get_selection() + tree_selection.set_mode(gtk.SELECTION_SINGLE) + + col0= gtk.TreeViewColumn('Image name') + cell0 = gtk.CellRendererText() + cell0.set_padding(5,2) + col0.pack_start(cell0, True) + col0.set_attributes(cell0, text=0) + col0.set_max_width(col0_width) + col0.set_min_width(col0_width) + imgsel_tv.append_column(col0) + + col1= gtk.TreeViewColumn('Select') + cell1 = gtk.CellRendererToggle() + cell1.set_padding(5,2) + cell1.connect("toggled", cls._toggle_single_cb, imgsel_tv, 1) + col1.pack_start(cell1, True) + col1.set_attributes(cell1, active=1) + col1.set_max_width(col1_width) + col1.set_min_width(col1_width) + imgsel_tv.append_column(col1) + + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scroll.set_shadow_type(gtk.SHADOW_IN) + scroll.add(imgsel_tv) + + vbox.pack_start(scroll, expand=True, fill=True) + + return vbox, imgsel_tv + + @classmethod + def gen_images_widget(cls, col0_width, col1_width, col2_width): + vbox = gtk.VBox(False, 10) + + imgsel_tv = gtk.TreeView() + imgsel_tv.set_rules_hint(True) + imgsel_tv.set_headers_visible(False) + tree_selection = imgsel_tv.get_selection() + tree_selection.set_mode(gtk.SELECTION_SINGLE) + + col0= gtk.TreeViewColumn('Image name') + cell0 = gtk.CellRendererText() + cell0.set_padding(5,2) + col0.pack_start(cell0, True) + col0.set_attributes(cell0, text=0) + col0.set_max_width(col0_width) + col0.set_min_width(col0_width) + imgsel_tv.append_column(col0) + + col1= gtk.TreeViewColumn('Image size') + cell1 = gtk.CellRendererText() + cell1.set_padding(5,2) + col1.pack_start(cell1, True) + col1.set_attributes(cell1, text=1) + col1.set_max_width(col1_width) + col1.set_min_width(col1_width) + imgsel_tv.append_column(col1) + + col2= gtk.TreeViewColumn('Select') + cell2 = gtk.CellRendererToggle() + cell2.set_padding(5,2) + cell2.connect("toggled", cls._toggle_single_cb, imgsel_tv, 2) + col2.pack_start(cell2, True) + col2.set_attributes(cell2, active=2) + col2.set_max_width(col2_width) + col2.set_min_width(col2_width) + imgsel_tv.append_column(col2) + + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scroll.set_shadow_type(gtk.SHADOW_IN) + scroll.add(imgsel_tv) + + vbox.pack_start(scroll, expand=True, fill=True) + + return vbox, imgsel_tv + + @classmethod + def _on_add_item_clicked(cls, button, model): + new_item = ["##KEY##", "##VALUE##"] + + iter = model.append() + model.set (iter, + 0, new_item[0], + 1, new_item[1], + ) + + + @classmethod + def _on_remove_item_clicked(cls, button, treeview): + + selection = treeview.get_selection() + model, iter = selection.get_selected() + + if iter: + path = model.get_path(iter)[0] + model.remove(iter) + + @classmethod + def _on_cell_edited(cls, cell, path_string, new_text, model): + it = model.get_iter_from_string(path_string) + column = cell.get_data("column") + model.set(it, column, new_text) + + + @classmethod + def gen_editable_settings(cls, setting, tooltip=""): + setting_hbox = gtk.HBox(False, 10) + + vbox = gtk.VBox(False, 10) + setting_hbox.pack_start(vbox, expand=True, fill=True) + + setting_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) + for key in setting.keys(): + setting_store.set(setting_store.append(), 0, key, 1, setting[key]) + + setting_tree = gtk.TreeView(setting_store) + setting_tree.set_headers_visible(True) + setting_tree.set_size_request(300, 100) + + col = gtk.TreeViewColumn('Key') + col.set_min_width(100) + col.set_max_width(150) + col.set_resizable(True) + col1 = gtk.TreeViewColumn('Value') + col1.set_min_width(100) + col1.set_max_width(150) + col1.set_resizable(True) + setting_tree.append_column(col) + setting_tree.append_column(col1) + cell = gtk.CellRendererText() + cell.set_property('width-chars', 10) + cell.set_property('editable', True) + cell.set_data("column", 0) + cell.connect("edited", cls._on_cell_edited, setting_store) + cell1 = gtk.CellRendererText() + cell1.set_property('width-chars', 10) + cell1.set_property('editable', True) + cell1.set_data("column", 1) + cell1.connect("edited", cls._on_cell_edited, setting_store) + col.pack_start(cell, True) + col1.pack_end(cell1, True) + col.set_attributes(cell, text=0) + col1.set_attributes(cell1, text=1) + + scroll = gtk.ScrolledWindow() + scroll.set_shadow_type(gtk.SHADOW_IN) + scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scroll.add(setting_tree) + vbox.pack_start(scroll, expand=True, fill=True) + + # some buttons + hbox = gtk.HBox(True, 4) + vbox.pack_start(hbox, False, False) + + button = gtk.Button(stock=gtk.STOCK_ADD) + button.connect("clicked", cls._on_add_item_clicked, setting_store) + hbox.pack_start(button) + + button = gtk.Button(stock=gtk.STOCK_REMOVE) + button.connect("clicked", cls._on_remove_item_clicked, setting_tree) + hbox.pack_start(button) + + image = gtk.Image() + image.set_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON) + image.set_tooltip_text(tooltip) + setting_hbox.pack_start(image, expand=False, fill=False) + + return setting_hbox, setting_store + +class HobViewTable (gtk.VBox): + """ + A VBox to contain the table for different recipe views and package view + """ + def __init__(self, columns, reset_clicked_cb=None, toggled_cb=None): + gtk.VBox.__init__(self, False, 6) + self.table_tree = gtk.TreeView() + self.table_tree.set_headers_visible(True) + self.table_tree.set_headers_clickable(True) + self.table_tree.set_enable_search(True) + self.table_tree.set_search_column(0) + self.table_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) + + for i in range(len(columns)): + col = gtk.TreeViewColumn(columns[i]['col_name']) + col.set_clickable(True) + col.set_resizable(True) + col.set_sort_column_id(columns[i]['col_id']) + col.set_min_width(columns[i]['col_min']) + col.set_max_width(columns[i]['col_max']) + self.table_tree.append_column(col) + + if columns[i]['col_style'] == 'toggle': + cell = gtk.CellRendererToggle() + cell.set_property('activatable', True) + cell.connect("toggled", toggled_cb, self.table_tree) + col.pack_end(cell, True) + col.set_attributes(cell, active=columns[i]['col_id']) + elif columns[i]['col_style'] == 'text': + cell = gtk.CellRendererText() + col.pack_start(cell, True) + col.set_attributes(cell, text=columns[i]['col_id']) + + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) + scroll.set_shadow_type(gtk.SHADOW_IN) + scroll.add(self.table_tree) + self.pack_start(scroll, True, True, 0) + + hbox = gtk.HBox(False, 5) + button = gtk.Button("Reset") + button.connect('clicked', reset_clicked_cb) + hbox.pack_end(button, False, False, 0) + + self.pack_start(hbox, False, False, 0) + +class HobViewBar (gtk.EventBox): + """ + A EventBox with the specified gray background color is associated with a notebook. + And the toolbar to simulate the tabs. + """ + + def __init__(self, notebook): + if not notebook: + return + self.notebook = notebook + + # setup an event box + gtk.EventBox.__init__(self) + self.set_border_width(2) + style = self.get_style().copy() + style.bg[gtk.STATE_NORMAL] = self.get_colormap().alloc_color (HobColors.GRAY, False, False) + self.set_style(style) + + hbox = gtk.HBox() + self.add(hbox) + + # setup a tool bar in the event box + self.toolbar = gtk.Toolbar() + self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL) + self.toolbar.set_style(gtk.TOOLBAR_TEXT) + self.toolbar.set_border_width(5) + + self.toolbuttons = [] + for index in range(self.notebook.get_n_pages()): + child = self.notebook.get_nth_page(index) + label = self.notebook.get_tab_label_text(child) + tip_text = 'switch to ' + label + ' page' + toolbutton = self.toolbar.append_element(gtk.TOOLBAR_CHILD_RADIOBUTTON, None, + label, tip_text, "Private text", None, + self.toolbutton_cb, index) + toolbutton.set_size_request(200, 100) + self.toolbuttons.append(toolbutton) + + # set the default current page + self.modify_toolbuttons_bg(0) + self.notebook.set_current_page(0) + + self.toolbar.append_space() + + # add the tool bar into the event box + hbox.pack_start(self.toolbar, expand=False, fill=False) + + self.search = gtk.Entry() + self.align = gtk.Alignment(xalign=0.5, yalign=0.5) + self.align.add(self.search) + hbox.pack_end(self.align, expand=False, fill=False) + + self.label = gtk.Label(" Search: ") + self.label.set_alignment(0.5, 0.5) + hbox.pack_end(self.label, expand=False, fill=False) + + def toolbutton_cb(self, widget, index): + if index >= self.notebook.get_n_pages(): + return + self.notebook.set_current_page(index) + self.modify_toolbuttons_bg(index) + + def modify_toolbuttons_bg(self, index): + if index >= len(self.toolbuttons): + return + for i in range(0, len(self.toolbuttons)): + toolbutton = self.toolbuttons[i] + if i == index: + self.modify_toolbutton_bg(toolbutton, True) + else: + self.modify_toolbutton_bg(toolbutton) + + def modify_toolbutton_bg(self, toolbutton, active=False): + if active: + toolbutton.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.WHITE)) + toolbutton.modify_bg(gtk.STATE_ACTIVE, gtk.gdk.Color(HobColors.WHITE)) + toolbutton.modify_bg(gtk.STATE_SELECTED, gtk.gdk.Color(HobColors.WHITE)) + toolbutton.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(HobColors.WHITE)) + else: + toolbutton.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.GRAY)) + toolbutton.modify_bg(gtk.STATE_ACTIVE, gtk.gdk.Color(HobColors.GRAY)) + toolbutton.modify_bg(gtk.STATE_SELECTED, gtk.gdk.Color(HobColors.GRAY)) + toolbutton.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(HobColors.GRAY)) + +class HobXpmLabelButtonBox(gtk.EventBox): + """ label: name of buttonbox + description: the simple description + """ + + def __init__(self, display_file="", hover_file="", label="", description=""): + gtk.EventBox.__init__(self) + self._base_state_flags = gtk.STATE_NORMAL + self.set_events(gtk.gdk.MOTION_NOTIFY | gtk.gdk.BUTTON_PRESS | gtk.gdk.EXPOSE) + + self.connect("expose-event", self.cb) + self.connect("enter-notify-event", self.pointer_enter_cb) + self.connect("leave-notify-event", self.pointer_leave_cb) + + self.icon_hover = gtk.Image() + self.icon_hover.set_name("icon_image") + if type(hover_file) == str: + pixbuf = gtk.gdk.pixbuf_new_from_file(hover_file) + self.icon_hover.set_from_pixbuf(pixbuf) + + self.icon_display = gtk.Image() + self.icon_display.set_name("icon_image") + if type(display_file) == str: + pixbuf = gtk.gdk.pixbuf_new_from_file(display_file) + self.icon_display.set_from_pixbuf(pixbuf) + + self.tb = gtk.Table(2, 10, True) + self.tb.set_row_spacing(1, False) + self.tb.set_col_spacing(1, False) + self.add(self.tb) + self.tb.attach(self.icon_display, 0, 2, 0, 2, 0, 0) + self.tb.attach(self.icon_hover, 0, 2, 0, 2, 0, 0) + + lbl = gtk.Label() + lbl.set_alignment(0.0, 0.5) + lbl.set_markup("%s" % label) + self.tb.attach(lbl, 2, 10, 0, 1) + + lbl = gtk.Label() + lbl.set_alignment(0.0, 0.5) + lbl.set_markup("%s" % description) + self.tb.attach(lbl, 2, 10, 1, 2) + + def pointer_enter_cb(self, *args): + #if not self.is_focus(): + self.set_state(gtk.STATE_PRELIGHT) + self._base_state_flags = gtk.STATE_PRELIGHT + self.icon_hover.show() + self.icon_display.hide() + + def pointer_leave_cb(self, *args): + self.set_state(gtk.STATE_NORMAL) + self._base_state_flags = gtk.STATE_NORMAL + self.icon_display.show() + self.icon_hover.hide() + + def cb(self, w,e): + """ Hide items - first time """ + pass + diff --git a/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py b/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py new file mode 100644 index 0000000000..d3a9ffd710 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/imageconfigurationpage.py @@ -0,0 +1,358 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import glib +from bb.ui.crumbs.progressbar import HobProgressBar +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.hobwidget import hic, HobXpmLabelButtonBox +from bb.ui.crumbs.hoblistmodel import RecipeListModel +from bb.ui.crumbs.hobpages import HobPage + +from bb.ui.crumbs.hig import CrumbsDialog, BinbDialog, \ + AdvancedSettingDialog, LayerSelectionDialog + +# +# ImageConfigurationPage +# +class ImageConfigurationPage (HobPage): + + __dummy_machine__ = "--select a machine--" + + def __init__(self, builder): + super(ImageConfigurationPage, self).__init__(builder, "Image configuration") + + self.image_combo_id = None + self.create_visual_elements() + + def create_visual_elements(self): + # create visual elements + self.toolbar = gtk.Toolbar() + self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL) + self.toolbar.set_style(gtk.TOOLBAR_BOTH) + + _, template_button = self.append_toolbar_button(self.toolbar, + "Template", + hic.ICON_TEMPLATES_DISPLAY_FILE, + hic.ICON_TEMPLATES_HOVER_FILE, + "Load a hob building template saved before", + self.template_button_clicked_cb) + _, my_images_button = self.append_toolbar_button(self.toolbar, + "My images", + hic.ICON_IMAGES_DISPLAY_FILE, + hic.ICON_IMAGES_HOVER_FILE, + "Open images built out previously for running or deployment", + self.my_images_button_clicked_cb) + _, settings_button = self.append_toolbar_button(self.toolbar, + "Settings", + hic.ICON_SETTINGS_DISPLAY_FILE, + hic.ICON_SETTINGS_HOVER_FILE, + "Other advanced settings for build", + self.settings_button_clicked_cb) + + self.config_top_button = self.add_onto_top_bar(self.toolbar) + + self.gtable = gtk.Table(40, 40, True) + self.create_config_machine() + self.create_config_baseimg() + self.config_build_button = self.create_config_build_button() + + def _remove_all_widget(self): + children = self.gtable.get_children() or [] + for child in children: + self.gtable.remove(child) + children = self.box_group_area.get_children() or [] + for child in children: + self.box_group_area.remove(child) + children = self.get_children() or [] + for child in children: + self.remove(child) + + def _pack_components(self): + self._remove_all_widget() + self.pack_start(self.config_top_button, expand=False, fill=False) + self.pack_start(self.group_align, expand=True, fill=True) + + self.box_group_area.pack_start(self.gtable, expand=True, fill=True) + self.box_group_area.pack_end(self.config_build_button, expand=False, fill=False) + + def show_machine(self): + self._pack_components() + self.set_config_machine_layout() + self.show_all() + self.progress_bar.reset() + self.progress_bar.hide() + self.config_build_button.hide_all() + + def update_progress_bar(self, title, fraction, status=True): + self.progress_bar.update(fraction) + self.progress_bar.set_title(title) + self.progress_bar.set_rcstyle(status) + + def show_info_populating(self): + self._pack_components() + self.set_config_machine_layout() + self.show_all() + self.config_build_button.hide_all() + + def show_info_populated(self): + self._pack_components() + self.set_config_machine_layout() + self.set_config_baseimg_layout() + self.show_all() + self.progress_bar.reset() + self.progress_bar.hide() + + def create_config_machine(self): + self.machine_title = gtk.Label() + self.machine_title.set_alignment(0.0, 0.5) + mark = "Select a machine" % self.span_tag('24px', 'bold') + self.machine_title.set_markup(mark) + + self.machine_title_desc = gtk.Label() + self.machine_title_desc.set_alignment(0, 0.5) + mark = ("This is the profile of the target machine for which you" + " are building the image.\n") % (self.span_tag(px='14px')) + self.machine_title_desc.set_markup(mark) + + self.machine_combo = gtk.combo_box_new_text() + self.machine_combo.connect("changed", self.machine_combo_changed_cb) + + icon_file = hic.ICON_LAYERS_DISPLAY_FILE + hover_file = hic.ICON_LAYERS_HOVER_FILE + self.layer_button = HobXpmLabelButtonBox(icon_file, hover_file, + "Layers", "Add support for machines, software, etc") + self.layer_button.connect("button-release-event", self.layer_button_clicked_cb) + + icon_file = hic.ICON_INFO_DISPLAY_FILE + self.layer_info_icon = gtk.Image() + pix_buffer = gtk.gdk.pixbuf_new_from_file(icon_file) + self.layer_info_icon.set_from_pixbuf(pix_buffer) + markup = "Layers are a powerful mechanism to extend the Yocto Project " + markup += "with your own functionality.\n" + markup += "For more on layers, check:\n" + markup += "http://www.yoctoproject.org/docs/current/poky-ref-manual/" + markup += "poky-ref-manual.html#usingpoky-changes-layers." + self.layer_info_icon.set_tooltip_markup(markup) + + self.progress_bar = HobProgressBar() + self.machine_separator = gtk.HSeparator() + + def set_config_machine_layout(self): + self.gtable.attach(self.machine_title, 0, 40, 0, 4) + self.gtable.attach(self.machine_title_desc, 0, 40, 4, 6) + self.gtable.attach(self.machine_combo, 0, 12, 6, 9) + self.gtable.attach(self.layer_button, 12, 36, 6, 10) + self.gtable.attach(self.layer_info_icon, 36, 40, 6, 9) + self.gtable.attach(self.progress_bar, 0, 40, 13, 17) + self.gtable.attach(self.machine_separator, 0, 40, 12, 13) + + def create_config_baseimg(self): + self.image_title = gtk.Label() + self.image_title.set_alignment(0, 1.0) + mark = "Select a base image" % self.span_tag('24px', 'bold') + self.image_title.set_markup(mark) + + self.image_title_desc = gtk.Label() + self.image_title_desc.set_alignment(0, 0.5) + mark = ("Base images are a starting point for the type of image you want. " + "You can build them as \n" + "they are or customize them to your specific needs.\n") % self.span_tag('14px') + self.image_title_desc.set_markup(mark) + + self.image_combo = gtk.combo_box_new_text() + self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb) + + self.image_desc = gtk.Label() + self.image_desc.set_alignment(0, 0) + self.image_desc.set_line_wrap(True) + + # button to view recipes + icon_file = hic.ICON_RCIPE_DISPLAY_FILE + hover_file = hic.ICON_RCIPE_HOVER_FILE + self.view_recipes_button = HobXpmLabelButtonBox(icon_file, hover_file, + "View Recipes", "Add/remove recipes and collections") + self.view_recipes_button.connect("button-release-event", self.view_recipes_button_clicked_cb) + + # button to view packages + icon_file = hic.ICON_PACKAGES_DISPLAY_FILE + hover_file = hic.ICON_PACKAGES_HOVER_FILE + self.view_packages_button = HobXpmLabelButtonBox(icon_file, hover_file, + "View Packages", "Add/remove packages") + self.view_packages_button.connect("button-release-event", self.view_packages_button_clicked_cb) + + self.image_separator = gtk.HSeparator() + + def set_config_baseimg_layout(self): + self.gtable.attach(self.image_title, 0, 40, 13, 17) + self.gtable.attach(self.image_title_desc, 0, 40, 17, 22) + self.gtable.attach(self.image_combo, 0, 12, 22, 25) + self.gtable.attach(self.image_desc, 14, 38, 22, 27) + self.gtable.attach(self.view_recipes_button, 0, 20, 28, 32) + self.gtable.attach(self.view_packages_button, 20, 40, 28, 32) + self.gtable.attach(self.image_separator, 0, 40, 35, 36) + + def create_config_build_button(self): + # Create the "Build packages" and "Just bake" buttons at the bottom + button_box = gtk.HBox(False, 5) + + # create button "Just bake" + just_bake_button = gtk.Button() + label = gtk.Label() + mark = "Just bake" % self.span_tag('24px', 'bold') + label.set_markup(mark) + + just_bake_button.set_image(label) + just_bake_button.set_size_request(205, 49) + just_bake_button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.ORANGE)) + just_bake_button.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(HobColors.ORANGE)) + just_bake_button.modify_bg(gtk.STATE_SELECTED, gtk.gdk.Color(HobColors.ORANGE)) + just_bake_button.set_tooltip_text("Build image to get your target image") + just_bake_button.set_flags(gtk.CAN_DEFAULT) + just_bake_button.grab_default() + just_bake_button.connect("clicked", self.just_bake_button_clicked_cb) + button_box.pack_end(just_bake_button, expand=False, fill=False) + + label = gtk.Label(" or ") + button_box.pack_end(label, expand=False, fill=False) + + # create button "Build Packages" + build_packages_button = gtk.LinkButton("Build packages first based on recipe selection " + "for late customization on packages for the target image", "Build Packages") + build_packages_button.connect("clicked", self.build_packages_button_clicked_cb) + button_box.pack_end(build_packages_button, expand=False, fill=False) + + return button_box + + def machine_combo_changed_cb(self, machine_combo): + combo_item = machine_combo.get_active_text() + if not combo_item or combo_item == self.__dummy_machine__: + self.builder.switch_page(self.builder.MACHINE_SELECTION) + else: + self.builder.configuration.curr_mach = combo_item + # Do reparse recipes + self.builder.switch_page(self.builder.RCPPKGINFO_POPULATING) + + def update_machine_combo(self): + all_machines = [self.__dummy_machine__] + self.builder.parameters.all_machines + + model = self.machine_combo.get_model() + model.clear() + for machine in all_machines: + self.machine_combo.append_text(machine) + self.machine_combo.set_active(0) + + def switch_machine_combo(self): + model = self.machine_combo.get_model() + active = 0 + while active < len(model): + if model[active][0] == self.builder.configuration.curr_mach: + self.machine_combo.set_active(active) + return + active += 1 + self.machine_combo.set_active(0) + + def image_combo_changed_idle_cb(self, selected_image, selected_recipes, selected_packages): + self.builder.update_recipe_model(selected_image, selected_recipes) + self.builder.update_package_model(selected_packages) + self.builder.window_sensitive(True) + + def image_combo_changed_cb(self, combo): + self.builder.window_sensitive(False) + selected_image = self.image_combo.get_active_text() + if not selected_image: + return + + selected_recipes = [] + + image_path = self.builder.recipe_model.pn_path[selected_image] + image_iter = self.builder.recipe_model.get_iter(image_path) + selected_packages = self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_INSTALL).split() + + mark = ("%s\n") % (self.span_tag('14px'), self.builder.recipe_model.get_value(image_iter, self.builder.recipe_model.COL_DESC)) + self.image_desc.set_markup(mark) + + self.builder.recipe_model.reset() + self.builder.package_model.reset() + + glib.idle_add(self.image_combo_changed_idle_cb, selected_image, selected_recipes, selected_packages) + + def _image_combo_connect_signal(self): + if not self.image_combo_id: + self.image_combo_id = self.image_combo.connect("changed", self.image_combo_changed_cb) + + def _image_combo_disconnect_signal(self): + if self.image_combo_id: + self.image_combo.disconnect(self.image_combo_id) + self.image_combo_id = None + + def update_image_combo(self, recipe_model, selected_image): + # Update the image combo according to the images in the recipe_model + # populate image combo + filter = {RecipeListModel.COL_TYPE : ['image']} + image_model = recipe_model.tree_model(filter) + active = 0 + cnt = 0 + + it = image_model.get_iter_first() + self._image_combo_disconnect_signal() + model = self.image_combo.get_model() + model.clear() + # append and set active + while it: + path = image_model.get_path(it) + image_name = image_model[path][recipe_model.COL_NAME] + self.image_combo.append_text(image_name) + if image_name == selected_image: + active = cnt + it = image_model.iter_next(it) + cnt = cnt + 1 + self._image_combo_connect_signal() + + self.image_combo.set_active(-1) + self.image_combo.set_active(active) + + def layer_button_clicked_cb(self, event, data): + # Create a layer selection dialog + self.builder.show_layer_selection_dialog() + + def view_recipes_button_clicked_cb(self, event, data): + self.builder.show_recipes() + + def view_packages_button_clicked_cb(self, event, data): + self.builder.show_packages() + + def just_bake_button_clicked_cb(self, button): + self.builder.just_bake() + + def build_packages_button_clicked_cb(self, button): + self.builder.build_packages() + + def template_button_clicked_cb(self, button): + self.builder.show_load_template_dialog() + + def my_images_button_clicked_cb(self, button): + self.builder.show_load_my_images_dialog() + + def settings_button_clicked_cb(self, button): + # Create an advanced settings dialog + self.builder.show_adv_settings_dialog() diff --git a/bitbake/lib/bb/ui/crumbs/imagedetailspage.py b/bitbake/lib/bb/ui/crumbs/imagedetailspage.py new file mode 100755 index 0000000000..e8419e0ee9 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/imagedetailspage.py @@ -0,0 +1,294 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gobject +import gtk +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.hobwidget import hic, HobWidget +from bb.ui.crumbs.hobpages import HobPage + +# +# ImageDetailsPage +# +class ImageDetailsPage (HobPage): + + class DetailBox (gtk.EventBox): + def __init__(self, varlist, vallist, icon = None, button = None, color = HobColors.LIGHT_GRAY): + gtk.EventBox.__init__(self) + + # set color + style = self.get_style().copy() + style.bg[gtk.STATE_NORMAL] = self.get_colormap().alloc_color(color, False, False) + self.set_style(style) + + self.hbox = gtk.HBox() + self.hbox.set_border_width(15) + self.add(self.hbox) + + # pack the icon and the text on the left + row = len(varlist) + self.table = gtk.Table(row, 20, True) + self.table.set_size_request(100, -1) + self.hbox.pack_start(self.table, expand=True, fill=True, padding=15) + + colid = 0 + if icon != None: + self.table.attach(icon, colid, colid + 2, 0, 1) + colid = colid + 2 + for line in range(0, row): + self.table.attach(self.text2label(varlist[line], vallist[line]), colid, 20, line, line + 1) + + # pack the button on the right + if button != None: + self.hbox.pack_end(button, expand=False, fill=False) + + def text2label(self, variable, value): + # append the name:value to the left box + # such as "Name: hob-core-minimal-variant-2011-12-15-beagleboard" + markup = "%s" % variable + markup += "%s" % value + label = gtk.Label() + label.set_alignment(0.0, 0.5) + label.set_markup(markup) + return label + + def __init__(self, builder): + super(ImageDetailsPage, self).__init__(builder, "Image details") + + self.image_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN) + self.create_visual_elements() + + def create_visual_elements(self): + # create visual elements + # create the toolbar + self.toolbar = gtk.Toolbar() + self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL) + self.toolbar.set_style(gtk.TOOLBAR_BOTH) + + _, my_images_button = self.append_toolbar_button(self.toolbar, + "My images", + hic.ICON_IMAGES_DISPLAY_FILE, + hic.ICON_IMAGES_HOVER_FILE, + "Open images built out previously for running or deployment", + self.my_images_button_clicked_cb) + + self.details_top_buttons = self.add_onto_top_bar(self.toolbar) + + def _remove_all_widget(self): + children = self.get_children() or [] + for child in children: + self.remove(child) + children = self.box_group_area.get_children() or [] + for child in children: + self.box_group_area.remove(child) + + def _size_to_string(self, size): + if len(str(int(size))) > 6: + size_str = '%.1f' % (size*1.0/(1024*1024)) + ' MB' + elif len(str(int(size))) > 3: + size_str = '%.1f' % (size*1.0/1024) + ' KB' + else: + size_str = str(size) + ' B' + return size_str + + def show_page(self, step): + build_succeeded = (step == self.builder.IMAGE_GENERATED) + image_addr = self.builder.parameters.image_addr + image_names = self.builder.parameters.image_names + if build_succeeded: + image_addr = self.builder.parameters.image_addr + image_names = self.builder.parameters.image_names + machine = self.builder.configuration.curr_mach + base_image = self.builder.recipe_model.get_selected_image() + layers = self.builder.configuration.layers + pkg_num = "%s" % len(self.builder.package_model.get_selected_packages()) + else: + pkg_num = "N/A" + + self._remove_all_widget() + self.pack_start(self.details_top_buttons, expand=False, fill=False) + self.pack_start(self.group_align, expand=True, fill=True) + + if build_succeeded: + # building is the previous step + icon = gtk.Image() + pixmap_path = hic.ICON_INDI_CONFIRM_FILE + color = HobColors.RUNNING + pix_buffer = gtk.gdk.pixbuf_new_from_file(pixmap_path) + icon.set_from_pixbuf(pix_buffer) + varlist = [""] + vallist = ["Your image is ready"] + build_result = self.DetailBox(varlist=varlist, vallist=vallist, icon=icon, button=None, color=color) + self.box_group_area.pack_start(build_result, expand=False, fill=False) + + # Name + self.image_store.clear() + for image_name in image_names: + image_size = self._size_to_string(os.stat(os.path.join(image_addr, image_name)).st_size) + self.image_store.set(self.image_store.append(), 0, image_name, 1, image_size, 2, False) + images_widget, treeview = HobWidget.gen_images_widget(600, 200, 100) + treeview.set_model(self.image_store) + self.box_group_area.pack_start(images_widget, expand=False, fill=False) + + # Machine, Base image and Layers + layer_num_limit = 15 + varlist = ["Machine: ", "Base image: ", "Layers: "] + vallist = [] + if build_succeeded: + vallist.append(machine) + vallist.append(base_image) + i = 0 + for layer in layers: + varlist.append(" - ") + if i > layer_num_limit: + break + i += 1 + vallist.append("") + i = 0 + for layer in layers: + if i > layer_num_limit: + break + elif i == layer_num_limit: + vallist.append("and more...") + else: + vallist.append(layer) + i += 1 + + edit_config_button = gtk.LinkButton("Changes settings for build", "Edit configuration") + edit_config_button.connect("clicked", self.edit_config_button_clicked_cb) + setting_detail = self.DetailBox(varlist=varlist, vallist=vallist, icon=None, button=edit_config_button) + self.box_group_area.pack_start(setting_detail, expand=False, fill=False) + + # Packages included, and Total image size + varlist = ["Packages included: ", "Total image size: "] + vallist = [] + vallist.append(pkg_num) + vallist.append(image_size) + if build_succeeded: + edit_packages_button = gtk.LinkButton("Change package selection for customization", "Edit packages") + edit_packages_button.connect("clicked", self.edit_packages_button_clicked_cb) + else: # get to this page from "My images" + edit_packages_button = None + package_detail = self.DetailBox(varlist=varlist, vallist=vallist, icon=None, button=edit_packages_button) + self.box_group_area.pack_start(package_detail, expand=False, fill=False) + if build_succeeded: + buttonlist = ["Build new image", "Save as template", "Run image", "Deploy image"] + else: # get to this page from "My images" + buttonlist = ["Build new image", "Run image", "Deploy image"] + details_bottom_buttons = self.create_bottom_buttons(buttonlist) + self.box_group_area.pack_end(details_bottom_buttons, expand=False, fill=False) + + self.show_all() + + def create_bottom_buttons(self, buttonlist): + # Create the buttons at the bottom + bottom_buttons = gtk.HBox(False, 5) + created = False + + # create button "Deploy image" + name = "Deploy image" + if name in buttonlist: + deploy_button = gtk.Button() + label = gtk.Label() + mark = "Deploy image" % self.span_tag('24px', 'bold') + label.set_markup(mark) + deploy_button.set_image(label) + deploy_button.set_size_request(205, 49) + deploy_button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.ORANGE)) + deploy_button.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(HobColors.ORANGE)) + deploy_button.modify_bg(gtk.STATE_SELECTED, gtk.gdk.Color(HobColors.ORANGE)) + deploy_button.set_tooltip_text("Deploy image to get your target board") + deploy_button.set_flags(gtk.CAN_DEFAULT) + deploy_button.grab_default() + deploy_button.connect("clicked", self.deploy_button_clicked_cb) + bottom_buttons.pack_end(deploy_button, expand=False, fill=False) + created = True + + name = "Run image" + if name in buttonlist: + if created == True: + # separator + label = gtk.Label(" or ") + bottom_buttons.pack_end(label, expand=False, fill=False) + + # create button "Run image" + run_button = gtk.LinkButton("Launch and boot the image in the QEMU emulator", "Run image") + run_button.connect("clicked", self.run_button_clicked_cb) + bottom_buttons.pack_end(run_button, expand=False, fill=False) + created = True + + name = "Save as template" + if name in buttonlist: + if created == True: + # separator + label = gtk.Label(" or ") + bottom_buttons.pack_end(label, expand=False, fill=False) + + # create button "Save as template" + save_button = gtk.LinkButton("Save the hob build template for future use", "Save as template") + save_button.connect("clicked", self.save_button_clicked_cb) + bottom_buttons.pack_end(save_button, expand=False, fill=False) + create = True + + name = "Build new image" + if name in buttonlist: + # create button "Build new image" + build_new_button = gtk.LinkButton("Initiate another new build from the beginning", "Build new image") + build_new_button.connect("clicked", self.build_new_button_clicked_cb) + bottom_buttons.pack_start(build_new_button, expand=False, fill=False) + + return bottom_buttons + + def _get_selected_image(self): + image_name = "" + iter = self.image_store.get_iter_first() + while iter: + path = self.image_store.get_path(iter) + if self.image_store[path][2]: + image_name = self.image_store[path][0] + break + iter = self.image_store.iter_next(iter) + + return image_name + + def save_button_clicked_cb(self, button): + self.builder.show_save_template_dialog() + + def deploy_button_clicked_cb(self, button): + image_name = self._get_selected_image() + self.builder.deploy_image(image_name) + + def run_button_clicked_cb(self, button): + image_name = self._get_selected_image() + self.builder.runqemu_image(image_name) + + def build_new_button_clicked_cb(self, button): + self.builder.initiate_new_build() + + def edit_config_button_clicked_cb(self, button): + self.builder.show_configuration() + + def edit_packages_button_clicked_cb(self, button): + self.builder.show_packages(ask=False) + + def my_images_button_clicked_cb(self, button): + self.builder.show_load_my_images_dialog() diff --git a/bitbake/lib/bb/ui/crumbs/layereditor.py b/bitbake/lib/bb/ui/crumbs/layereditor.py deleted file mode 100644 index f5394a5f52..0000000000 --- a/bitbake/lib/bb/ui/crumbs/layereditor.py +++ /dev/null @@ -1,153 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011 Intel Corporation -# -# Authored by Joshua Lock -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import gobject -import gtk -from bb.ui.crumbs.configurator import Configurator -from bb.ui.crumbs.hig import CrumbsDialog - -class LayerEditor(gtk.Dialog): - """ - Gtk+ Widget for enabling and disabling layers. - Layers are added through using an open dialog to find the layer.conf - Disabled layers are deleted from conf/bblayers.conf - """ - def __init__(self, configurator, parent=None): - gtk.Dialog.__init__(self, "Layers", None, - gtk.DIALOG_DESTROY_WITH_PARENT, - (gtk.STOCK_CLOSE, gtk.RESPONSE_OK)) - - # We want to show a little more of the treeview in the default, - # emptier, case - self.set_size_request(-1, 300) - self.set_border_width(6) - self.vbox.set_property("spacing", 0) - self.action_area.set_property("border-width", 6) - - self.configurator = configurator - self.newly_added = {} - - # Label to inform users that meta is enabled but that you can't - # disable it as it'd be a *bad* idea - msg = "As the core of the build system the meta layer must always be included and therefore can't be viewed or edited here." - lbl = gtk.Label() - lbl.show() - lbl.set_use_markup(True) - lbl.set_markup(msg) - lbl.set_line_wrap(True) - lbl.set_justify(gtk.JUSTIFY_FILL) - self.vbox.pack_start(lbl, expand=False, fill=False, padding=6) - - # Create a treeview in which to list layers - # ListStore of Name, Path, Enabled - self.layer_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN) - self.tv = gtk.TreeView(self.layer_store) - self.tv.set_headers_visible(True) - - col0 = gtk.TreeViewColumn('Name') - self.tv.append_column(col0) - col1 = gtk.TreeViewColumn('Path') - self.tv.append_column(col1) - col2 = gtk.TreeViewColumn('Enabled') - self.tv.append_column(col2) - - cell0 = gtk.CellRendererText() - col0.pack_start(cell0, True) - col0.set_attributes(cell0, text=0) - cell1 = gtk.CellRendererText() - col1.pack_start(cell1, True) - col1.set_attributes(cell1, text=1) - cell2 = gtk.CellRendererToggle() - cell2.connect("toggled", self._toggle_layer_cb) - col2.pack_start(cell2, True) - col2.set_attributes(cell2, active=2) - - self.tv.show() - self.vbox.pack_start(self.tv, expand=True, fill=True, padding=0) - - tb = gtk.Toolbar() - tb.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR) - tb.set_style(gtk.TOOLBAR_BOTH) - tb.set_tooltips(True) - tb.show() - icon = gtk.Image() - icon.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_SMALL_TOOLBAR) - icon.show() - tb.insert_item("Add Layer", "Add new layer", None, icon, - self._find_layer_cb, None, -1) - self.vbox.pack_start(tb, expand=False, fill=False, padding=0) - - def set_parent_window(self, parent): - self.set_transient_for(parent) - - def load_current_layers(self, data): - for layer, path in self.configurator.enabled_layers.items(): - if layer != 'meta': - self.layer_store.append([layer, path, True]) - - def save_current_layers(self): - self.configurator.writeLayerConf() - - def _toggle_layer_cb(self, cell, path): - name = self.layer_store[path][0] - toggle = not self.layer_store[path][2] - if toggle: - self.configurator.addLayer(name, path) - else: - self.configurator.disableLayer(name) - self.layer_store[path][2] = toggle - - def _find_layer_cb(self, button): - self.find_layer(self) - - def find_layer(self, parent): - def conf_error(parent, lbl): - dialog = CrumbsDialog(parent, lbl) - dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) - response = dialog.run() - dialog.destroy() - - dialog = gtk.FileChooserDialog("Add new layer", parent, - gtk.FILE_CHOOSER_ACTION_OPEN, - (gtk.STOCK_CANCEL, gtk.RESPONSE_NO, - gtk.STOCK_OPEN, gtk.RESPONSE_YES)) - label = gtk.Label("Select the layer.conf of the layer you wish to add") - label.show() - dialog.set_extra_widget(label) - response = dialog.run() - path = dialog.get_filename() - dialog.destroy() - - lbl = "Error\nUnable to load layer %s because " % path - if response == gtk.RESPONSE_YES: - # FIXME: verify we've actually got a layer conf? - if path.endswith("layer.conf"): - name, layerpath = self.configurator.addLayerConf(path) - if name and layerpath: - self.newly_added[name] = layerpath - self.layer_store.append([name, layerpath, True]) - return - elif name: - return - else: - lbl += "there was a problem parsing the layer.conf." - else: - lbl += "it is not a layer.conf file." - conf_error(parent, lbl) diff --git a/bitbake/lib/bb/ui/crumbs/packageselectionpage.py b/bitbake/lib/bb/ui/crumbs/packageselectionpage.py new file mode 100755 index 0000000000..8a8ab7585e --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/packageselectionpage.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import glib +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.hobwidget import HobViewBar, HobViewTable +from bb.ui.crumbs.hoblistmodel import PackageListModel +from bb.ui.crumbs.hobpages import HobPage + +# +# PackageSelectionPage +# +class PackageSelectionPage (HobPage): + + pages = [ + { + 'name' : 'All packages', + 'filter' : {}, + 'columns' : [{ + 'col_name' : 'Name', + 'col_id' : PackageListModel.COL_NAME, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400 + }, { + 'col_name' : 'size', + 'col_id' : PackageListModel.COL_SIZE, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 500 + }, { + 'col_name' : 'Included', + 'col_id' : PackageListModel.COL_INC, + 'col_style': 'toggle', + 'col_min' : 50, + 'col_max' : 50 + }] + }, { + 'name' : 'Included', + 'filter' : { PackageListModel.COL_INC : [True] }, + 'columns' : [{ + 'col_name' : 'Name', + 'col_id' : PackageListModel.COL_NAME, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 300 + }, { + 'col_name' : 'Brought by', + 'col_id' : PackageListModel.COL_BINB, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 350 + }, { + 'col_name' : 'size', + 'col_id' : PackageListModel.COL_SIZE, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 300 + }, { + 'col_name' : 'Included', + 'col_id' : PackageListModel.COL_INC, + 'col_style': 'toggle', + 'col_min' : 50, + 'col_max' : 50 + }] + } + ] + + def __init__(self, builder): + super(PackageSelectionPage, self).__init__(builder, "Package Selection") + + # set invisiable members + self.package_model = self.builder.package_model + + # create visual elements + self.create_visual_elements() + + def create_visual_elements(self): + self.label = gtk.Label("Packages included: 0\nSelected packages size: 0 MB") + self.eventbox = self.add_onto_top_bar(self.label, 73) + self.pack_start(self.eventbox, expand=False, fill=False) + self.pack_start(self.group_align, expand=True, fill=True) + + # set visiable members + self.grid = gtk.Table(10, 1, True) + self.grid.set_col_spacings(3) + + self.ins = gtk.Notebook() + self.ins.set_show_tabs(False) + self.tables = [] # we need to modify table when the dialog is shown + # append the tab + for i in range(len(self.pages)): + columns = self.pages[i]['columns'] + tab = HobViewTable(columns, self.reset_clicked_cb, self.table_toggled_cb) + filter = self.pages[i]['filter'] + tab.table_tree.set_model(self.package_model.tree_model(filter)) + label = gtk.Label(self.pages[i]['name']) + self.ins.append_page(tab, label) + self.tables.append(tab) + + self.grid.attach(self.ins, 0, 1, 1, 10, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND, 1, 1) + # a black bar associated with the notebook + self.topbar = HobViewBar(self.ins) + self.grid.attach(self.topbar, 0, 1, 0, 1, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND, 1, 1) + # set the search entry for each table + for tab in self.tables: + tab.table_tree.set_search_entry(self.topbar.search) + + inctab_tree_view = self.tables[len(self.pages)-1].table_tree + inctab_tree_selection = inctab_tree_view.get_selection() + inctab_tree_selection.connect("changed", self.tree_selection_cb, inctab_tree_view) + + # add all into the dialog + self.box_group_area.add(self.grid) + + button_box = gtk.HBox(False, 5) + self.box_group_area.pack_start(button_box, expand=False, fill=False) + + self.build_image_button = gtk.Button() + label = gtk.Label() + mark = "Build image" % self.span_tag('24px', 'bold') + label.set_markup(mark) + self.build_image_button.set_image(label) + self.build_image_button.set_size_request(205, 49) + self.build_image_button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.ORANGE)) + self.build_image_button.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(HobColors.ORANGE)) + self.build_image_button.modify_bg(gtk.STATE_SELECTED, gtk.gdk.Color(HobColors.ORANGE)) + self.build_image_button.set_tooltip_text("Build image to get your target image") + self.build_image_button.set_flags(gtk.CAN_DEFAULT) + self.build_image_button.grab_default() + self.build_image_button.connect("clicked", self.build_image_clicked_cb) + button_box.pack_end(self.build_image_button, expand=False, fill=False) + + self.back_button = gtk.LinkButton("Go back to Image Configuration screen", "<< Back to image configuration") + self.back_button.connect("clicked", self.back_button_clicked_cb) + button_box.pack_start(self.back_button, expand=False, fill=False) + + def tree_selection_cb(self, tree_selection, tree_view): + tree_model = tree_view.get_model() + path, column = tree_view.get_cursor() + if not path or column == tree_view.get_column(2): + return + + it = tree_model.get_iter(path) + binb = tree_model.get_value(it, PackageListModel.COL_BINB) + if binb: + self.builder.show_binb_dialog(binb) + + def build_image_clicked_cb(self, button): + self.builder.build_image() + + def back_button_clicked_cb(self, button): + self.builder.show_configuration() + + def _expand_all(self): + for tab in self.tables: + tab.table_tree.expand_all() + + def refresh_selection(self): + self._expand_all() + + self.builder.configuration.selected_packages = self.package_model.get_selected_packages() + selected_packages_num = len(self.builder.configuration.selected_packages) + selected_packages_size = float(self.package_model.get_packages_size()) + selected_packages_size_str = self._size_to_string(selected_packages_size) + + image_overhead_factor = self.builder.configuration.image_overhead_factor + image_rootfs_size = self.builder.configuration.image_rootfs_size + image_extra_size = self.builder.configuration.image_extra_size + base_size = image_overhead_factor * selected_packages_size + image_total_size = max(base_size, image_rootfs_size) + image_extra_size + image_total_size_str = self._size_to_string(image_total_size) + + self.label.set_text("Packages included: %s\nSelected packages size: %s\nTotal image size: %s" % + (selected_packages_num, selected_packages_size_str, image_total_size_str)) + + """ + Helper function to convert the package size to string format. + The unit of size is KB + """ + def _size_to_string(self, size): + if len(str(int(size))) > 3: + size_str = '%.1f' % (size*1.0/1024) + ' MB' + else: + size_str = str(size) + ' KB' + return size_str + + # Callback functions + def reset_clicked_cb(self, button): + self.package_model.reset() + self.builder.reset_package_model() + + def toggle_item_idle_cb(self, path): + if not self.package_model.path_included(path): + self.package_model.include_item(item_path=path, binb="User Selected") + else: + self.package_model.exclude_item(item_path=path) + + self.builder.window_sensitive(True) + + def table_toggled_cb(self, cell, view_path, view_tree): + # Click to include a package + self.builder.window_sensitive(False) + view_model = view_tree.get_model() + path = self.package_model.convert_vpath_to_path(view_model, view_path) + glib.idle_add(self.toggle_item_idle_cb, path) diff --git a/bitbake/lib/bb/ui/crumbs/progress.py b/bitbake/lib/bb/ui/crumbs/progress.py deleted file mode 100644 index 0c7ad963b5..0000000000 --- a/bitbake/lib/bb/ui/crumbs/progress.py +++ /dev/null @@ -1,20 +0,0 @@ -import gtk - -class ProgressBar(gtk.Dialog): - def __init__(self, parent): - - gtk.Dialog.__init__(self, flags=(gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)) - self.set_title("Parsing metadata, please wait...") - self.set_default_size(500, 0) - self.set_transient_for(parent) - self.progress = gtk.ProgressBar() - self.vbox.pack_start(self.progress) - self.show_all() - - def update(self, x, y): - self.progress.set_fraction(float(x)/float(y)) - self.progress.set_text("%2d %%" % (x*100/y)) - - def pulse(self): - self.progress.set_text("Loading...") - self.progress.pulse() diff --git a/bitbake/lib/bb/ui/crumbs/progressbar.py b/bitbake/lib/bb/ui/crumbs/progressbar.py new file mode 100644 index 0000000000..882d461711 --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/progressbar.py @@ -0,0 +1,52 @@ +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011 Intel Corporation +# +# Authored by Shane Wang +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +from bb.ui.crumbs.hobcolor import HobColors + +class HobProgressBar (gtk.ProgressBar): + def __init__(self): + gtk.ProgressBar.__init__(self) + self.set_rcstyle(True) + self.percentage = 0 + + def set_rcstyle(self, status): + rcstyle = gtk.RcStyle() + rcstyle.fg[2] = gtk.gdk.Color(HobColors.BLACK) + if status: + rcstyle.bg[3] = gtk.gdk.Color(HobColors.RUNNING) + else: + rcstyle.bg[3] = gtk.gdk.Color(HobColors.ERROR) + self.modify_style(rcstyle) + + def set_title(self, text=None): + if not text: + text = "" + text += " %.0f%%" % self.percentage + self.set_text(text) + + def reset(self): + self.set_fraction(0) + self.set_text("") + self.set_rcstyle(True) + self.percentage = 0 + + def update(self, fraction): + self.percentage = int(fraction * 100) + self.set_fraction(fraction) diff --git a/bitbake/lib/bb/ui/crumbs/recipeselectionpage.py b/bitbake/lib/bb/ui/crumbs/recipeselectionpage.py new file mode 100755 index 0000000000..73b8a1e4ef --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/recipeselectionpage.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2012 Intel Corporation +# +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import glib +from bb.ui.crumbs.hobcolor import HobColors +from bb.ui.crumbs.hobwidget import HobWidget, HobViewBar, HobViewTable +from bb.ui.crumbs.hoblistmodel import RecipeListModel +from bb.ui.crumbs.hobpages import HobPage + +# +# RecipeSelectionPage +# +class RecipeSelectionPage (HobPage): + pages = [ + { + 'name' : 'Recipe', + 'filter' : { RecipeListModel.COL_TYPE : ['recipe'] }, + 'columns' : [{ + 'col_name' : 'Recipe', + 'col_id' : RecipeListModel.COL_NAME, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400 + }, { + 'col_name' : 'License', + 'col_id' : RecipeListModel.COL_LIC, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400 + }, { + 'col_name' : 'Group', + 'col_id' : RecipeListModel.COL_GROUP, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400 + }, { + 'col_name' : 'Included', + 'col_id' : RecipeListModel.COL_INC, + 'col_style': 'toggle', + 'col_min' : 50, + 'col_max' : 50 + }] + }, { + 'name' : 'Recipe Collection', + 'filter' : { RecipeListModel.COL_TYPE : ['task'] }, + 'columns' : [{ + 'col_name' : 'Recipe Collection', + 'col_id' : RecipeListModel.COL_NAME, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400 + }, { + 'col_name' : 'Description', + 'col_id' : RecipeListModel.COL_DESC, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400 + }, { + 'col_name' : 'Included', + 'col_id' : RecipeListModel.COL_INC, + 'col_style': 'toggle', + 'col_min' : 50, + 'col_max' : 50 + }] + }, { + 'name' : 'Included', + 'filter' : { RecipeListModel.COL_INC : [True], + RecipeListModel.COL_TYPE : ['recipe', 'task'] }, + 'columns' : [{ + 'col_name' : 'Recipe', + 'col_id' : RecipeListModel.COL_NAME, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 400 + }, { + 'col_name' : 'Brought by', + 'col_id' : RecipeListModel.COL_BINB, + 'col_style': 'text', + 'col_min' : 100, + 'col_max' : 500 + }, { + 'col_name' : 'Included', + 'col_id' : RecipeListModel.COL_INC, + 'col_style': 'toggle', + 'col_min' : 50, + 'col_max' : 50 + }] + } + ] + + def __init__(self, builder = None): + super(RecipeSelectionPage, self).__init__(builder, "Recipe Selection") + + # set invisiable members + self.recipe_model = self.builder.recipe_model + + # create visual elements + self.create_visual_elements() + + def create_visual_elements(self): + self.label = gtk.Label("Recipes included: %s" % len(self.builder.configuration.selected_recipes)) + self.eventbox = self.add_onto_top_bar(self.label, 73) + self.pack_start(self.eventbox, expand=False, fill=False) + self.pack_start(self.group_align, expand=True, fill=True) + + # set visiable members + self.grid = gtk.Table(10, 1, True) + self.grid.set_col_spacings(3) + + # draw the left part of the window + # a notebook + self.ins = gtk.Notebook() + self.ins.set_show_tabs(False) + self.tables = [] # we need modify table when the dialog is shown + # append the tabs in order + for i in range(len(self.pages)): + columns = self.pages[i]['columns'] + tab = HobViewTable(columns, self.reset_clicked_cb, self.table_toggled_cb) + filter = self.pages[i]['filter'] + tab.table_tree.set_model(self.recipe_model.tree_model(filter)) + label = gtk.Label(self.pages[i]['name']) + self.ins.append_page(tab, label) + self.tables.append(tab) + + self.grid.attach(self.ins, 0, 1, 1, 10, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND) + # a black bar associated with the notebook + self.topbar = HobViewBar(self.ins) + self.grid.attach(self.topbar, 0, 1, 0, 1, gtk.FILL | gtk.EXPAND, gtk.FILL | gtk.EXPAND) + # set the search entry for each table + for tab in self.tables: + tab.table_tree.set_search_entry(self.topbar.search) + + inctab_tree_view = self.tables[len(self.pages)-1].table_tree + inctab_tree_selection = inctab_tree_view.get_selection() + inctab_tree_selection.connect("changed", self.tree_selection_cb, inctab_tree_view) + + # add all into the window + self.box_group_area.add(self.grid) + + button_box = gtk.HBox(False, 5) + self.box_group_area.pack_end(button_box, expand=False, fill=False) + + self.build_packages_button = gtk.Button() + label = gtk.Label() + mark = "Build packages" % self.span_tag('24px', 'bold') + label.set_markup(mark) + self.build_packages_button.set_image(label) + self.build_packages_button.set_size_request(205, 49) + self.build_packages_button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(HobColors.ORANGE)) + self.build_packages_button.modify_bg(gtk.STATE_PRELIGHT, gtk.gdk.Color(HobColors.ORANGE)) + self.build_packages_button.modify_bg(gtk.STATE_SELECTED, gtk.gdk.Color(HobColors.ORANGE)) + self.build_packages_button.set_tooltip_text("Build packages for customization") + self.build_packages_button.set_flags(gtk.CAN_DEFAULT) + self.build_packages_button.grab_default() + self.build_packages_button.connect("clicked", self.build_packages_clicked_cb) + button_box.pack_end(self.build_packages_button, expand=False, fill=False) + + self.back_button = gtk.LinkButton("Go back to Image Configuration screen", "<< Back to image configuration") + self.back_button.connect("clicked", self.back_button_clicked_cb) + button_box.pack_start(self.back_button, expand=False, fill=False) + + def tree_selection_cb(self, tree_selection, tree_view): + tree_model = tree_view.get_model() + path, column = tree_view.get_cursor() + if not path or column == tree_view.get_column(2): + return + + it = tree_model.get_iter(path) + binb = tree_model.get_value(it, RecipeListModel.COL_BINB) + if binb: + self.builder.show_binb_dialog(binb) + + def build_packages_clicked_cb(self, button): + self.builder.build_packages() + + def back_button_clicked_cb(self, button): + self.builder.show_configuration() + + def refresh_selection(self): + self.builder.configuration.selected_image = self.recipe_model.get_selected_image() + _, self.builder.configuration.selected_recipes = self.recipe_model.get_selected_recipes() + self.label.set_text("Recipes included: %s" % len(self.builder.configuration.selected_recipes)) + + # Callback functions + def reset_clicked_cb(self, button): + self.builder.reset_recipe_model() + + def toggle_item_idle_cb(self, path): + if not self.recipe_model.path_included(path): + self.recipe_model.include_item(item_path=path, binb="User Selected", image_contents=False) + else: + self.recipe_model.exclude_item(item_path=path) + + self.builder.window_sensitive(True) + + def table_toggled_cb(self, cell, view_path, view_tree): + # Click to include a recipe + self.builder.window_sensitive(False) + view_model = view_tree.get_model() + path = self.recipe_model.convert_vpath_to_path(view_model, view_path) + glib.idle_add(self.toggle_item_idle_cb, path) diff --git a/bitbake/lib/bb/ui/crumbs/runningbuild.py b/bitbake/lib/bb/ui/crumbs/runningbuild.py index 4f609fc622..718f692412 100644 --- a/bitbake/lib/bb/ui/crumbs/runningbuild.py +++ b/bitbake/lib/bb/ui/crumbs/runningbuild.py @@ -25,12 +25,7 @@ import logging import time import urllib import urllib2 - -class Colors(object): - OK = "#ffffff" - RUNNING = "#aaffaa" - WARNING ="#f88017" - ERROR = "#ffaaaa" +from bb.ui.crumbs.hobcolor import HobColors class RunningBuildModel (gtk.TreeStore): (COL_LOG, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_COLOR, COL_NUM_ACTIVE) = range(7) @@ -58,7 +53,10 @@ class RunningBuild (gobject.GObject): ()), 'build-complete' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, - ()) + ()), + 'task-started' : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), } pids_to_task = {} tasks_to_iter = {} @@ -108,13 +106,13 @@ class RunningBuild (gobject.GObject): if event.levelno >= logging.ERROR: icon = "dialog-error" - color = Colors.ERROR + color = HobColors.ERROR elif event.levelno >= logging.WARNING: icon = "dialog-warning" - color = Colors.WARNING + color = HobColors.WARNING else: icon = None - color = Colors.OK + color = HobColors.OK # if we know which package we belong to, we'll append onto its list. # otherwise, we'll jump to the top of the master list @@ -152,7 +150,7 @@ class RunningBuild (gobject.GObject): None, "Package: %s" % (package), None, - Colors.OK, + HobColors.OK, 0)) self.tasks_to_iter[(package, None)] = parent @@ -160,7 +158,7 @@ class RunningBuild (gobject.GObject): # such. # @todo if parent is already in error, don't mark it green self.model.set(parent, self.model.COL_ICON, "gtk-execute", - self.model.COL_COLOR, Colors.RUNNING) + self.model.COL_COLOR, HobColors.RUNNING) # Add an entry in the model for this task i = self.model.append (parent, (None, @@ -168,7 +166,7 @@ class RunningBuild (gobject.GObject): task, "Task: %s" % (task), "gtk-execute", - Colors.RUNNING, + HobColors.RUNNING, 0)) # update the parent's active task count @@ -179,10 +177,6 @@ class RunningBuild (gobject.GObject): # that we need to attach to a task. self.tasks_to_iter[(package, task)] = i - # If we don't handle these the GUI does not proceed - elif isinstance(event, bb.build.TaskInvalid): - return - elif isinstance(event, bb.build.TaskBase): current = self.tasks_to_iter[(package, task)] parent = self.tasks_to_iter[(package, None)] @@ -194,20 +188,20 @@ class RunningBuild (gobject.GObject): if isinstance(event, bb.build.TaskFailed): # Mark the task and parent as failed icon = "dialog-error" - color = Colors.ERROR + color = HobColors.ERROR logfile = event.logfile if logfile and os.path.exists(logfile): with open(logfile) as f: logdata = f.read() - self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', Colors.OK, 0)) + self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', HobColors.OK, 0)) for i in (current, parent): self.model.set(i, self.model.COL_ICON, icon, self.model.COL_COLOR, color) else: icon = None - color = Colors.OK + color = HobColors.OK # Mark the task as inactive self.model.set(current, self.model.COL_ICON, icon, @@ -219,7 +213,7 @@ class RunningBuild (gobject.GObject): if self.model.get(parent, self.model.COL_ICON) != 'dialog-error': self.model.set(parent, self.model.COL_ICON, icon) if num_active == 0: - self.model.set(parent, self.model.COL_COLOR, Colors.OK) + self.model.set(parent, self.model.COL_COLOR, HobColors.OK) # Clear the iters and the pids since when the task goes away the # pid will no longer be used for messages @@ -234,8 +228,12 @@ class RunningBuild (gobject.GObject): None, "Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'), None, - Colors.OK, + HobColors.OK, 0)) + if pbar: + pbar.update(0, None, bb.event.getName(event)) + pbar.set_title() + elif isinstance(event, bb.event.BuildCompleted): failures = int (event._failures) self.model.prepend(None, (None, @@ -243,7 +241,7 @@ class RunningBuild (gobject.GObject): None, "Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'), None, - Colors.OK, + HobColors.OK, 0)) # Emit the appropriate signal depending on the number of failures @@ -254,6 +252,8 @@ class RunningBuild (gobject.GObject): # Emit a generic "build-complete" signal for things wishing to # handle when the build is finished self.emit("build-complete") + if pbar: + pbar.set_text(event.msg) elif isinstance(event, bb.command.CommandFailed): if event.error.startswith("Exited with"): @@ -280,6 +280,15 @@ class RunningBuild (gobject.GObject): pbar.update(event.current, self.progress_total) elif isinstance(event, bb.event.ParseCompleted) and pbar: pbar.hide() + #using runqueue events as many as possible to update the progress bar + elif isinstance(event, (bb.runqueue.runQueueTaskStarted, bb.runqueue.sceneQueueTaskStarted)): + message = {} + message["eventname"] = bb.event.getName(event) + num_of_completed = event.stats.completed + event.stats.failed + message["current"] = num_of_completed + message["total"] = event.stats.total + message["title"] = "" + self.emit("task-started", message) return diff --git a/bitbake/lib/bb/ui/crumbs/tasklistmodel.py b/bitbake/lib/bb/ui/crumbs/tasklistmodel.py deleted file mode 100644 index 90a7e5459c..0000000000 --- a/bitbake/lib/bb/ui/crumbs/tasklistmodel.py +++ /dev/null @@ -1,620 +0,0 @@ -# -# BitBake Graphical GTK User Interface -# -# Copyright (C) 2011 Intel Corporation -# -# Authored by Joshua Lock -# -# 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 -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import gtk -import gobject -import re - -class BuildRep(gobject.GObject): - - def __init__(self, userpkgs, allpkgs, base_image=None): - gobject.GObject.__init__(self) - self.base_image = base_image - self.allpkgs = allpkgs - self.userpkgs = userpkgs - - def loadRecipe(self, pathname): - contents = [] - packages = "" - base_image = "" - - with open(pathname, 'r') as f: - contents = f.readlines() - - pkg_pattern = "^\s*(IMAGE_INSTALL)\s*([+=.?]+)\s*(\".*?\")" - img_pattern = "^\s*(require)\s+(\S+.bb)" - - for line in contents: - matchpkg = re.search(pkg_pattern, line) - matchimg = re.search(img_pattern, line) - if matchpkg: - packages = packages + matchpkg.group(3).strip('"') - if matchimg: - base_image = os.path.basename(matchimg.group(2)).split(".")[0] - - self.base_image = base_image - self.userpkgs = packages - - def writeRecipe(self, writepath, model): - template = """ -# Recipe generated by the HOB - -require %s - -IMAGE_INSTALL += "%s" -""" - - empty_template = """ -# Recipe generated by the HOB - -inherit core-image - -IMAGE_INSTALL = "%s" -""" - if self.base_image and not self.base_image == "empty": - meta_path = model.find_image_path(self.base_image) - recipe = template % (meta_path, self.userpkgs) - else: - recipe = empty_template % self.allpkgs - - if os.path.exists(writepath): - os.rename(writepath, "%s~" % writepath) - - with open(writepath, 'w') as r: - r.write(recipe) - - return writepath - -class TaskListModel(gtk.ListStore): - """ - This class defines an gtk.ListStore subclass which will convert the output - of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also - providing convenience functions to access gtk.TreeModel subclasses which - provide filtered views of the data. - """ - (COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC, COL_IMG, COL_PATH, COL_PN) = range(11) - - __gsignals__ = { - "tasklist-populated" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - ()), - "contents-changed" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_INT,)), - "image-changed" : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, - (gobject.TYPE_STRING,)), - } - - """ - """ - def __init__(self): - self.contents = None - self.tasks = None - self.packages = None - self.images = None - self.selected_image = None - - gtk.ListStore.__init__ (self, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_STRING, - gobject.TYPE_BOOLEAN, - gobject.TYPE_BOOLEAN, - gobject.TYPE_STRING, - gobject.TYPE_STRING) - - """ - Helper method to determine whether name is a target pn - """ - def non_target_name(self, name): - if ('-native' in name) or ('-cross' in name) or name.startswith('virtual/'): - return True - return False - - def contents_changed_cb(self, tree_model, path, it=None): - pkg_cnt = self.contents.iter_n_children(None) - self.emit("contents-changed", pkg_cnt) - - def contents_model_filter(self, model, it): - if not model.get_value(it, self.COL_INC) or model.get_value(it, self.COL_TYPE) == 'image': - return False - name = model.get_value(it, self.COL_NAME) - if self.non_target_name(name): - return False - else: - return True - - """ - Create, if required, and return a filtered gtk.TreeModel - containing only the items which are to be included in the - image - """ - def contents_model(self): - if not self.contents: - self.contents = self.filter_new() - self.contents.set_visible_func(self.contents_model_filter) - self.contents.connect("row-inserted", self.contents_changed_cb) - self.contents.connect("row-deleted", self.contents_changed_cb) - return self.contents - - """ - Helper function to determine whether an item is a task - """ - def task_model_filter(self, model, it): - if model.get_value(it, self.COL_TYPE) == 'task': - return True - else: - return False - - """ - Create, if required, and return a filtered gtk.TreeModel - containing only the items which are tasks - """ - def tasks_model(self): - if not self.tasks: - self.tasks = self.filter_new() - self.tasks.set_visible_func(self.task_model_filter) - return self.tasks - - """ - Helper function to determine whether an item is an image - """ - def image_model_filter(self, model, it): - if model.get_value(it, self.COL_TYPE) == 'image': - return True - else: - return False - - """ - Create, if required, and return a filtered gtk.TreeModel - containing only the items which are images - """ - def images_model(self): - if not self.images: - self.images = self.filter_new() - self.images.set_visible_func(self.image_model_filter) - return self.images - - """ - Helper function to determine whether an item is a package - """ - def package_model_filter(self, model, it): - if model.get_value(it, self.COL_TYPE) != 'package': - return False - else: - name = model.get_value(it, self.COL_NAME) - if self.non_target_name(name): - return False - return True - - """ - Create, if required, and return a filtered gtk.TreeModel - containing only the items which are packages - """ - def packages_model(self): - if not self.packages: - self.packages = self.filter_new() - self.packages.set_visible_func(self.package_model_filter) - return self.packages - - """ - The populate() function takes as input the data from a - bb.event.TargetsTreeGenerated event and populates the TaskList. - Once the population is done it emits gsignal tasklist-populated - to notify any listeners that the model is ready - """ - def populate(self, event_model): - # First clear the model, in case repopulating - self.clear() - for item in event_model["pn"]: - atype = 'package' - name = item - summary = event_model["pn"][item]["summary"] - lic = event_model["pn"][item]["license"] - group = event_model["pn"][item]["section"] - filename = event_model["pn"][item]["filename"] - if ('task-' in name): - atype = 'task' - elif ('-image-' in name): - atype = 'image' - - # Create a combined list of build and runtime dependencies and - # then remove any duplicate entries and any entries for -dev - # packages - depends = event_model["depends"].get(item, []) - rdepends = event_model["rdepends-pn"].get(item, []) - packages = {} - for pkg in event_model["packages"]: - if event_model["packages"][pkg]["pn"] == name: - deps = [] - deps.extend(depends) - deps.extend(event_model["rdepends-pkg"].get(pkg, [])) - deps.extend(rdepends) - deps = self.squish(deps) - # rdepends-pn includes pn-dev - if ("%s-dev" % item) in deps: - deps.remove("%s-dev" % item) - # rdepends-on includes pn - if pkg in deps: - deps.remove(pkg) - packages[pkg] = deps - - for p in packages: - self.set(self.append(), self.COL_NAME, p, self.COL_DESC, summary, - self.COL_LIC, lic, self.COL_GROUP, group, - self.COL_DEPS, " ".join(packages[p]), self.COL_BINB, "", - self.COL_TYPE, atype, self.COL_INC, False, - self.COL_IMG, False, self.COL_PATH, filename, - self.COL_PN, item) - - self.emit("tasklist-populated") - - """ - Load a BuildRep into the model - """ - def load_image_rep(self, rep): - # Unset everything - it = self.get_iter_first() - while it: - path = self.get_path(it) - self[path][self.COL_INC] = False - self[path][self.COL_IMG] = False - it = self.iter_next(it) - - # Iterate the images and disable them all - it = self.images.get_iter_first() - while it: - path = self.images.convert_path_to_child_path(self.images.get_path(it)) - name = self[path][self.COL_NAME] - if name == rep.base_image: - self.include_item(path, image_contents=True) - else: - self[path][self.COL_INC] = False - it = self.images.iter_next(it) - - # Mark all of the additional packages for inclusion - packages = rep.userpkgs.split(" ") - it = self.get_iter_first() - while it: - path = self.get_path(it) - name = self[path][self.COL_NAME] - if name in packages: - self.include_item(path, binb="User Selected") - packages.remove(name) - it = self.iter_next(it) - - self.emit("image-changed", rep.base_image) - - """ - squish lst so that it doesn't contain any duplicate entries - """ - def squish(self, lst): - seen = {} - for l in lst: - seen[l] = 1 - return seen.keys() - - """ - Mark the item at path as not included - NOTE: - path should be a gtk.TreeModelPath into self (not a filtered model) - """ - def remove_item_path(self, path): - self[path][self.COL_BINB] = "" - self[path][self.COL_INC] = False - - """ - Recursively called to mark the item at opath and any package which - depends on it for removal. - NOTE: This method dumbly removes user selected packages and since we don't - do significant reverse dependency tracking it's easier and simpler to save - the items marked as user selected and re-add them once the removal sweep is - complete. - """ - def mark(self, opath): - usersel = {} - removed = [] - - it = self.get_iter_first() - # The name of the item we're removing, so that we can use it to find - # other items which either depend on it, or were brought in by it - marked_name = self[opath][self.COL_NAME] - - # Remove the passed item - self.remove_item_path(opath) - - # Remove all dependent packages, update binb - while it: - path = self.get_path(it) - it = self.iter_next(it) - - inc = self[path][self.COL_INC] - deps = self[path][self.COL_DEPS] - binb = self[path][self.COL_BINB].split(', ') - itype = self[path][self.COL_TYPE] - itname = self[path][self.COL_NAME] - - # We ignore anything that isn't a package - if not itype == "package": - continue - - # If the user added this item and it's not the item we're removing - # we should keep it and its dependencies, the easiest way to do so - # is to save its name and re-mark it for inclusion once dependency - # processing is complete - if "User Selected" in binb: - usersel[itname] = self[path][self.COL_IMG] - - # If the iterated item is included and depends on the removed - # item it should also be removed. - # FIXME: need to ensure partial name matching doesn't happen - if inc and marked_name in deps and itname not in removed: - # found a dependency, remove it - removed.append(itname) - self.mark(path) - - # If the iterated item was brought in by the removed (passed) item - # try and find an alternative dependee and update the binb column - if inc and marked_name in binb: - binb.remove(marked_name) - self[path][self.COL_BINB] = ', '.join(binb).lstrip(', ') - - # Re-add any removed user selected items - for u in usersel: - npath = self.find_path_for_item(u) - self.include_item(item_path=npath, - binb="User Selected", - image_contents=usersel[u]) - """ - Remove items from contents if the have an empty COL_BINB (brought in by) - caused by all packages they are a dependency of being removed. - If the item isn't a package we leave it included. - """ - def sweep_up(self): - it = self.contents.get_iter_first() - while it: - binb = self.contents.get_value(it, self.COL_BINB) - itype = self.contents.get_value(it, self.COL_TYPE) - remove = False - - if itype == 'package' and not binb: - oit = self.contents.convert_iter_to_child_iter(it) - opath = self.get_path(oit) - self.mark(opath) - remove = True - - # When we remove a package from the contents model we alter the - # model, so continuing to iterate is bad. *Furthermore* it's - # likely that the removal has affected an already iterated item - # so we should start from the beginning anyway. - # Only when we've managed to iterate the entire contents model - # without removing any items do we allow the loop to exit. - if remove: - it = self.contents.get_iter_first() - else: - it = self.contents.iter_next(it) - - """ - Check whether the item at item_path is included or not - """ - def contents_includes_path(self, item_path): - return self[item_path][self.COL_INC] - - """ - Add this item, and any of its dependencies, to the image contents - """ - def include_item(self, item_path, binb="", image_contents=False): - item_name = self[item_path][self.COL_NAME] - item_deps = self[item_path][self.COL_DEPS] - - self[item_path][self.COL_INC] = True - - item_bin = self[item_path][self.COL_BINB].split(', ') - if binb and not binb in item_bin: - item_bin.append(binb) - self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ') - - # We want to do some magic with things which are brought in by the - # base image so tag them as so - if image_contents: - self[item_path][self.COL_IMG] = True - if self[item_path][self.COL_TYPE] == 'image': - self.selected_image = item_name - - if item_deps: - # Ensure all of the items deps are included and, where appropriate, - # add this item to their COL_BINB - for dep in item_deps.split(" "): - # If the contents model doesn't already contain dep, add it - dep_path = self.find_path_for_item(dep) - if not dep_path: - continue - dep_included = self.contents_includes_path(dep_path) - - if dep_included and not dep in item_bin: - # don't set the COL_BINB to this item if the target is an - # item in our own COL_BINB - dep_bin = self[dep_path][self.COL_BINB].split(', ') - if not item_name in dep_bin: - dep_bin.append(item_name) - self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') - elif not dep_included: - self.include_item(dep_path, binb=item_name, image_contents=image_contents) - - """ - Find the model path for the item_name - Returns the path in the model or None - """ - def find_path_for_item(self, item_name): - # We don't include virtual/* or *-native items in the model so save a - # heavy iteration loop by exiting early for these items - if self.non_target_name(item_name): - return None - - it = self.get_iter_first() - while it: - if (self.get_value(it, self.COL_NAME) == item_name): - return self.get_path(it) - else: - it = self.iter_next(it) - return None - - """ - Empty self.contents by setting the include of each entry to None - """ - def reset(self): - # Deselect images - slightly more complex logic so that we don't - # have to iterate all of the contents of the main model, instead - # just iterate the images model. - if self.selected_image: - iit = self.images.get_iter_first() - while iit: - pit = self.images.convert_iter_to_child_iter(iit) - self.set(pit, self.COL_INC, False) - iit = self.images.iter_next(iit) - self.selected_image = None - - it = self.contents.get_iter_first() - while it: - oit = self.contents.convert_iter_to_child_iter(it) - self.set(oit, - self.COL_INC, False, - self.COL_BINB, "", - self.COL_IMG, False) - # As we've just removed the first item... - it = self.contents.get_iter_first() - - """ - Returns two lists. One of user selected packages and the other containing - all selected packages - """ - def get_selected_packages(self): - allpkgs = [] - userpkgs = [] - - it = self.contents.get_iter_first() - while it: - sel = "User Selected" in self.contents.get_value(it, self.COL_BINB) - name = self.contents.get_value(it, self.COL_NAME) - allpkgs.append(name) - if sel: - userpkgs.append(name) - it = self.contents.iter_next(it) - return userpkgs, allpkgs - - """ - Return a squished (uniquified) list of the PN's of all selected items - """ - def get_selected_pn(self): - pns = [] - - it = self.contents.get_iter_first() - while it: - if self.contents.get_value(it, self.COL_BINB): - pns.append(self.contents.get_value(it, self.COL_PN)) - it = self.contents.iter_next(it) - - return self.squish(pns) - - def image_contents_removed(self): - it = self.get_iter_first() - while it: - sel = self.get_value(it, self.COL_INC) - img = self.get_value(it, self.COL_IMG) - if img and not sel: - return True - it = self.iter_next(it) - return False - - def get_build_rep(self): - userpkgs, allpkgs = self.get_selected_packages() - # If base image contents have been removed start from an empty rootfs - if not self.selected_image or self.image_contents_removed(): - image = "empty" - else: - image = self.selected_image - - return BuildRep(" ".join(userpkgs), " ".join(allpkgs), image) - - def find_reverse_depends(self, pn): - revdeps = [] - it = self.contents.get_iter_first() - - while it: - name = self.contents.get_value(it, self.COL_NAME) - itype = self.contents.get_value(it, self.COL_TYPE) - deps = self.contents.get_value(it, self.COL_DEPS) - - it = self.contents.iter_next(it) - - if not itype == 'package': - continue - - if pn in deps: - revdeps.append(name) - - if pn in revdeps: - revdeps.remove(pn) - return revdeps - - def set_selected_image(self, img): - self.selected_image = img - path = self.find_path_for_item(img) - self.include_item(item_path=path, - binb="User Selected", - image_contents=True) - - self.emit("image-changed", self.selected_image) - - def set_selected_packages(self, pkglist): - selected = pkglist - it = self.get_iter_first() - - while it: - name = self.get_value(it, self.COL_NAME) - if name in pkglist: - pkglist.remove(name) - path = self.get_path(it) - self.include_item(item_path=path, - binb="User Selected") - if len(pkglist) == 0: - return - it = self.iter_next(it) - - def find_image_path(self, image): - it = self.images.get_iter_first() - - while it: - image_name = self.images.get_value(it, self.COL_NAME) - if image_name == image: - path = self.images.get_value(it, self.COL_PATH) - meta_pattern = "(\S*)/(meta*/)(\S*)" - meta_match = re.search(meta_pattern, path) - if meta_match: - _, lyr, bbrel = path.partition(meta_match.group(2)) - if bbrel: - path = bbrel - return path - it = self.images.iter_next(it) diff --git a/bitbake/lib/bb/ui/crumbs/template.py b/bitbake/lib/bb/ui/crumbs/template.py new file mode 100644 index 0000000000..d0283546af --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/template.py @@ -0,0 +1,180 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011 Intel Corporation +# +# Authored by Shane Wang +# +# 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 +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gobject +import os +import re + +class File(gobject.GObject): + + def __init__(self, pathfilename, suffix): + if not pathfilename.endswith(suffix): + pathfilename = "%s%s" % (pathfilename, suffix) + gobject.GObject.__init__(self) + self.pathfilename = pathfilename + + def readFile(self): + if not os.path.isfile(self.pathfilename): + return None + if not os.path.exists(self.pathfilename): + return None + + with open(self.pathfilename, 'r') as f: + contents = f.readlines() + f.close() + + return contents + + def writeFile(self, contents): + if os.path.exists(self.pathfilename): + orig = "%s.orig" % self.pathfilename + if os.path.exists(orig): + os.remove(orig) + os.rename(self.pathfilename, orig) + + with open(self.pathfilename, 'w') as f: + f.write(contents) + f.close() + +class ConfigFile(File): + """ + This object does save general config file. (say bblayers.conf, or local.conf). Again, it is the base class for other template files and image bb files. + """ + def __init__(self, pathfilename, suffix = None, header = None): + if suffix: + File.__init__(self, pathfilename, suffix) + else: + File.__init__(self, pathfilename, ".conf") + if header: + self.header = header + else: + self.header = "# Config generated by the HOB\n\n" + self.dictionary = {} + + def setVar(self, var, val): + if isinstance(val, list): + liststr = "" + if val: + i = 0 + for value in val: + if i < len(val) - 1: + liststr += "%s " % value + else: + liststr += "%s" % value + i += 1 + self.dictionary[var] = liststr + else: + self.dictionary[var] = val + + def save(self): + contents = self.header + for var, val in self.dictionary.items(): + contents += "%s = \"%s\"\n" % (var, val) + File.writeFile(self, contents) + +class HobTemplateFile(ConfigFile): + """ + This object does save or load hob specific file. + """ + def __init__(self, pathfilename): + ConfigFile.__init__(self, pathfilename, ".hob", "# Hob Template generated by the HOB\n\n") + + def getVar(self, var): + if var in self.dictionary: + return self.dictionary[var] + else: + return "" + + def load(self): + contents = ConfigFile.readFile(self) + self.dictionary.clear() + + pattern = "^\s*(\S+)\s*=\s*(\".*?\")" + + for line in contents: + match = re.search(pattern, line) + if match: + var = match.group(1) + val = match.group(2).strip('"') + self.dictionary[var] = val + return self.dictionary + +class RecipeFile(ConfigFile): + """ + This object is for image bb file. + """ + def __init__(self, pathfilename): + ConfigFile.__init__(self, pathfilename, ".bb", "# Recipe generated by the HOB\n\ninherit core-image\n") + +class TemplateMgr(gobject.GObject): + + __gLocalVars__ = ["MACHINE", "PACKAGE_CLASSES", "DISTRO", "DL_DIR", "SSTATE_DIR", "SSTATE_MIRROR", "PARALLEL_MAKE", "BB_NUMBER_THREAD"] + __gBBLayersVars__ = ["BBLAYERS"] + __gRecipeVars__ = ["DEPENDS", "IMAGE_INSTALL"] + + def __init__(self): + gobject.GObject.__init__(self) + self.template_hob = None + self.bblayers_conf = None + self.local_conf = None + self.image_bb = None + + def open(self, filename, path): + self.template_hob = HobTemplateFile("%s/%s%s%s" % (path, "template-", filename, ".hob")) + self.bblayers_conf = ConfigFile("%s/%s%s%s" % (path, "bblayers-", filename, ".conf")) + self.local_conf = ConfigFile("%s/%s%s%s" % (path, "local-", filename, ".conf")) + self.image_bb = RecipeFile("%s/%s%s%s" % (path, "hob-image-", filename, ".bb")) + + def setVar(self, var, val): + if var in TemplateMgr.__gLocalVars__: + self.local_conf.setVar(var, val) + if var in TemplateMgr.__gBBLayersVars__: + self.bblayers_conf.setVar(var, val) + if var in TemplateMgr.__gRecipeVars__: + self.image_bb.setVar(var, val) + + self.template_hob.setVar(var, val) + + def save(self): + self.local_conf.save() + self.bblayers_conf.save() + self.image_bb.save() + self.template_hob.save() + + def load(self, path): + self.template_hob = HobTemplateFile(path) + self.dictionary = self.template_hob.load() + + def getVar(self, var): + return self.template_hob.getVar(var) + + def destroy(self): + if self.template_hob: + del self.template_hob + template_hob = None + if self.bblayers_conf: + del self.bblayers_conf + self.bblayers_conf = None + if self.local_conf: + del self.local_conf + self.local_conf = None + if self.image_bb: + del self.image_bb + self.image_bb = None diff --git a/bitbake/lib/bb/ui/hob.py b/bitbake/lib/bb/ui/hob.py old mode 100644 new mode 100755 index 0fcaad54a7..429bb750dd --- a/bitbake/lib/bb/ui/hob.py +++ b/bitbake/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 +# Authored by Dongxiao Xu # # 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 = "Error!\nThere was an unrecoverable error during the" - lbl = lbl + " %s 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 = "Reset your selections?\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 "]) - 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 = "No selections made\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 = "Build empty image or only packages?\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 = "No image output type selected\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 = "Build completed\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 + "\nBrowse folder of built images." % (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 = "Remove %s?\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 = "Remove %s?\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 = "Force Stop build?\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 = "Stop build?\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 = ''' - - - - - - - - - - - - - - - - - - - ''' - - 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) diff --git a/bitbake/lib/bb/ui/icons/images/images_display.png b/bitbake/lib/bb/ui/icons/images/images_display.png new file mode 100644 index 0000000000..a7f87101af Binary files /dev/null and b/bitbake/lib/bb/ui/icons/images/images_display.png differ diff --git a/bitbake/lib/bb/ui/icons/images/images_hover.png b/bitbake/lib/bb/ui/icons/images/images_hover.png new file mode 100644 index 0000000000..2d9cd99b8e Binary files /dev/null and b/bitbake/lib/bb/ui/icons/images/images_hover.png differ diff --git a/bitbake/lib/bb/ui/icons/indicators/alert.png b/bitbake/lib/bb/ui/icons/indicators/alert.png new file mode 100644 index 0000000000..d1c6f55a2f Binary files /dev/null and b/bitbake/lib/bb/ui/icons/indicators/alert.png differ diff --git a/bitbake/lib/bb/ui/icons/indicators/confirmation.png b/bitbake/lib/bb/ui/icons/indicators/confirmation.png new file mode 100644 index 0000000000..3a5402d1e3 Binary files /dev/null and b/bitbake/lib/bb/ui/icons/indicators/confirmation.png differ diff --git a/bitbake/lib/bb/ui/icons/indicators/denied.png b/bitbake/lib/bb/ui/icons/indicators/denied.png new file mode 100644 index 0000000000..ee35c7defa Binary files /dev/null and b/bitbake/lib/bb/ui/icons/indicators/denied.png differ diff --git a/bitbake/lib/bb/ui/icons/indicators/error.png b/bitbake/lib/bb/ui/icons/indicators/error.png new file mode 100644 index 0000000000..d06a8c151a Binary files /dev/null and b/bitbake/lib/bb/ui/icons/indicators/error.png differ diff --git a/bitbake/lib/bb/ui/icons/indicators/issues.png b/bitbake/lib/bb/ui/icons/indicators/issues.png new file mode 100644 index 0000000000..b0c7461334 Binary files /dev/null and b/bitbake/lib/bb/ui/icons/indicators/issues.png differ diff --git a/bitbake/lib/bb/ui/icons/indicators/refresh.png b/bitbake/lib/bb/ui/icons/indicators/refresh.png new file mode 100644 index 0000000000..eb6c419db8 Binary files /dev/null and b/bitbake/lib/bb/ui/icons/indicators/refresh.png differ diff --git a/bitbake/lib/bb/ui/icons/indicators/tick.png b/bitbake/lib/bb/ui/icons/indicators/tick.png new file mode 100644 index 0000000000..beaad361c3 Binary files /dev/null and b/bitbake/lib/bb/ui/icons/indicators/tick.png differ diff --git a/bitbake/lib/bb/ui/icons/info/info_display.png b/bitbake/lib/bb/ui/icons/info/info_display.png new file mode 100644 index 0000000000..439587bc8c Binary files /dev/null and b/bitbake/lib/bb/ui/icons/info/info_display.png differ diff --git a/bitbake/lib/bb/ui/icons/info/info_hover.png b/bitbake/lib/bb/ui/icons/info/info_hover.png new file mode 100644 index 0000000000..a1dd89d374 Binary files /dev/null and b/bitbake/lib/bb/ui/icons/info/info_hover.png differ diff --git a/bitbake/lib/bb/ui/icons/layers/layers_display.png b/bitbake/lib/bb/ui/icons/layers/layers_display.png new file mode 100644 index 0000000000..94f90098fc Binary files /dev/null and b/bitbake/lib/bb/ui/icons/layers/layers_display.png differ diff --git a/bitbake/lib/bb/ui/icons/layers/layers_hover.png b/bitbake/lib/bb/ui/icons/layers/layers_hover.png new file mode 100644 index 0000000000..5e5c3b93ec Binary files /dev/null and b/bitbake/lib/bb/ui/icons/layers/layers_hover.png differ diff --git a/bitbake/lib/bb/ui/icons/packages/packages_display.png b/bitbake/lib/bb/ui/icons/packages/packages_display.png new file mode 100644 index 0000000000..bd20c422d8 Binary files /dev/null and b/bitbake/lib/bb/ui/icons/packages/packages_display.png differ diff --git a/bitbake/lib/bb/ui/icons/packages/packages_hover.png b/bitbake/lib/bb/ui/icons/packages/packages_hover.png new file mode 100644 index 0000000000..f71db227d5 Binary files /dev/null and b/bitbake/lib/bb/ui/icons/packages/packages_hover.png differ diff --git a/bitbake/lib/bb/ui/icons/recipe/recipe_display.png b/bitbake/lib/bb/ui/icons/recipe/recipe_display.png new file mode 100644 index 0000000000..838ef00c8d Binary files /dev/null and b/bitbake/lib/bb/ui/icons/recipe/recipe_display.png differ diff --git a/bitbake/lib/bb/ui/icons/recipe/recipe_hover.png b/bitbake/lib/bb/ui/icons/recipe/recipe_hover.png new file mode 100644 index 0000000000..246bfd5b34 Binary files /dev/null and b/bitbake/lib/bb/ui/icons/recipe/recipe_hover.png differ diff --git a/bitbake/lib/bb/ui/icons/settings/settings_display.png b/bitbake/lib/bb/ui/icons/settings/settings_display.png new file mode 100644 index 0000000000..88c464db04 Binary files /dev/null and b/bitbake/lib/bb/ui/icons/settings/settings_display.png differ diff --git a/bitbake/lib/bb/ui/icons/settings/settings_hover.png b/bitbake/lib/bb/ui/icons/settings/settings_hover.png new file mode 100644 index 0000000000..d92a0bf2c3 Binary files /dev/null and b/bitbake/lib/bb/ui/icons/settings/settings_hover.png differ diff --git a/bitbake/lib/bb/ui/icons/templates/templates_display.png b/bitbake/lib/bb/ui/icons/templates/templates_display.png new file mode 100644 index 0000000000..153c7afb62 Binary files /dev/null and b/bitbake/lib/bb/ui/icons/templates/templates_display.png differ diff --git a/bitbake/lib/bb/ui/icons/templates/templates_hover.png b/bitbake/lib/bb/ui/icons/templates/templates_hover.png new file mode 100644 index 0000000000..afb7165fe5 Binary files /dev/null and b/bitbake/lib/bb/ui/icons/templates/templates_hover.png differ diff --git a/bitbake/lib/bb/ui/uihelper.py b/bitbake/lib/bb/ui/uihelper.py index 617d60db82..bbf5135b70 100644 --- a/bitbake/lib/bb/ui/uihelper.py +++ b/bitbake/lib/bb/ui/uihelper.py @@ -40,3 +40,45 @@ class BBUIHelper: def getTasks(self): self.needUpdate = False return (self.running_tasks, self.failed_tasks) + + def findServerDetails(self): + import sys + import optparse + from bb.server.xmlrpc import BitbakeServerInfo, BitBakeServerConnection + host = "" + port = 0 + bind = "" + parser = optparse.OptionParser( + usage = """%prog -H host -P port -B bindaddr""") + + parser.add_option("-H", "--host", help = "Bitbake server's IP address", + action = "store", dest = "host", default = None) + + parser.add_option("-P", "--port", help = "Bitbake server's Port number", + action = "store", dest = "port", default = None) + + parser.add_option("-B", "--bind", help = "Hob2 local bind address", + action = "store", dest = "bind", default = None) + + options, args = parser.parse_args(sys.argv) + for key, val in options.__dict__.items(): + if key == 'host' and val: + host = val + elif key == 'port' and val: + port = int(val) + elif key == 'bind' and val: + bind = val + + if not host or not port or not bind: + parser.print_usage() + sys.exit(1) + + serverinfo = BitbakeServerInfo(host, port) + clientinfo = (bind, 0) + connection = BitBakeServerConnection(serverinfo, clientinfo) + + server = connection.connection + eventHandler = connection.events + + return server, eventHandler, host, bind + -- cgit v1.2.3-54-g00ecf