class HobHandler(gobject.GObject): """ This object does BitBake event handling for the hob gui. """ __gsignals__ = { "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,)), "sanity-failed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_INT)), "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,)), "recipe-populated" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), "package-populated" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), "network-passed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), "network-failed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } (GENERATE_CONFIGURATION, GENERATE_RECIPES, GENERATE_PACKAGES, GENERATE_IMAGE, POPULATE_PACKAGEINFO, SANITY_CHECK, NETWORK_TEST) = range(7) (SUB_PATH_LAYERS, SUB_FILES_DISTRO, SUB_FILES_MACH, SUB_FILES_SDKMACH, SUB_MATCH_CLASS, SUB_PARSE_CONFIG, SUB_SANITY_CHECK, SUB_GNERATE_TGTS, SUB_GENERATE_PKGINFO, SUB_BUILD_RECIPES, SUB_BUILD_IMAGE, SUB_NETWORK_TEST) = range(12) def __init__(self, server, recipe_model, package_model): super(HobHandler, self).__init__() self.build = RunningBuild(sequential=True) self.recipe_model = recipe_model self.package_model = package_model self.commands_async = [] self.generating = False self.current_phase = None self.building = False self.recipe_queue = [] self.package_queue = [] self.server = server self.error_msg = "" self.initcmd = None def set_busy(self): if not self.generating: self.emit("generating-data") self.generating = True def clear_busy(self): if self.generating: self.emit("data-generated") self.generating = False def runCommand(self, commandline): try: result, error = self.server.runCommand(commandline) if error: raise Exception("Error running command '%s': %s" % (commandline, error)) return result except Exception as e: self.commands_async = [] self.clear_busy() self.emit("command-failed", "Hob Exception - %s" % (str(e))) return None 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.SUB_PATH_LAYERS: self.runCommand(["findConfigFilePath", "bblayers.conf"]) elif next_command == self.SUB_FILES_DISTRO: self.runCommand(["findConfigFiles", "DISTRO"]) elif next_command == self.SUB_FILES_MACH: self.runCommand(["findConfigFiles", "MACHINE"]) elif next_command == self.SUB_FILES_SDKMACH: self.runCommand(["findConfigFiles", "MACHINE-SDK"]) elif next_command == self.SUB_MATCH_CLASS: self.runCommand(["findFilesMatchingInDir", "rootfs_", "classes"]) elif next_command == self.SUB_PARSE_CONFIG: self.runCommand(["parseConfigurationFiles", "", ""]) elif next_command == self.SUB_GNERATE_TGTS: self.runCommand(["generateTargetsTree", "classes/image.bbclass", []]) elif next_command == self.SUB_GENERATE_PKGINFO: self.runCommand(["triggerEvent", "bb.event.RequestPackageInfo()"]) elif next_command == self.SUB_SANITY_CHECK: self.runCommand(["triggerEvent", "bb.event.SanityCheck()"]) elif next_command == self.SUB_NETWORK_TEST: self.runCommand(["triggerEvent", "bb.event.NetworkTest()"]) elif next_command == self.SUB_BUILD_RECIPES: self.clear_busy() self.building = True self.runCommand(["buildTargets", self.recipe_queue, self.default_task]) self.recipe_queue = [] elif next_command == self.SUB_BUILD_IMAGE: self.clear_busy() self.building = True targets = [self.image] if self.package_queue: self.runCommand(["setVariable", "LINGUAS_INSTALL", ""]) self.runCommand(["setVariable", "PACKAGE_INSTALL", " ".join(self.package_queue)]) if self.toolchain_packages: self.runCommand(["setVariable", "TOOLCHAIN_TARGET_TASK", " ".join(self.toolchain_packages)]) targets.append(self.toolchain) if targets[0] == "hob-image": hobImage = self.runCommand(["matchFile", "hob-image.bb"]) if self.base_image != "Create your own image": baseImage = self.runCommand(["matchFile", self.base_image + ".bb"]) version = self.runCommand(["generateNewImage", hobImage, baseImage, self.package_queue]) targets[0] += version self.recipe_model.set_custom_image_version(version) self.runCommand(["buildTargets", targets, self.default_task]) def display_error(self): self.clear_busy() self.emit("command-failed", self.error_msg) self.error_msg = "" if self.building: self.building = False def handle_event(self, event): if not event: return if self.building: self.current_phase = "building" self.build.handle_event(event) if isinstance(event, bb.event.PackageInfo): self.package_model.populate(event._pkginfolist) self.emit("package-populated") self.run_next_command() elif isinstance(event, bb.event.SanityCheckPassed): self.run_next_command() elif isinstance(event, bb.event.SanityCheckFailed): self.emit("sanity-failed", event._msg, event._network_error) elif isinstance(event, logging.LogRecord): if not self.building: if event.levelno >= logging.ERROR: formatter = bb.msg.BBLogFormatter() msg = formatter.format(event) self.error_msg += msg + '\n' elif isinstance(event, bb.event.TargetsTreeGenerated): self.current_phase = "data generation" if event._model: self.recipe_model.populate(event._model) self.emit("recipe-populated") elif isinstance(event, bb.event.ConfigFilesFound): self.current_phase = "configuration lookup" var = event._variable values = event._values values.sort() self.emit("config-updated", var, values) elif isinstance(event, bb.event.ConfigFilePathFound): self.current_phase = "configuration lookup" elif isinstance(event, bb.event.FilesMatchingFound): self.current_phase = "configuration lookup" # FIXME: hard coding, should at least be a variable shared between # here and the caller if event._pattern == "rootfs_": formats = [] for match in event._matches: classname, sep, cls = match.rpartition(".") fs, sep, format = classname.rpartition("_") formats.append(format) formats.sort() self.emit("package-formats-updated", formats) elif isinstance(event, bb.command.CommandCompleted): self.current_phase = None self.run_next_command() elif isinstance(event, bb.command.CommandFailed): self.commands_async = [] self.display_error() 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) elif isinstance(event, bb.event.NetworkTestFailed): self.emit("network-failed") self.run_next_command() elif isinstance(event, bb.event.NetworkTestPassed): self.emit("network-passed") self.run_next_command() if self.error_msg and not self.commands_async: self.display_error() return def init_cooker(self): self.runCommand(["initCooker"]) def set_extra_inherit(self, bbclass): inherits = self.runCommand(["getVariable", "INHERIT"]) or "" inherits = inherits + " " + bbclass self.runCommand(["setVariable", "INHERIT", inherits]) def set_bblayers(self, bblayers): self.runCommand(["setVariable", "BBLAYERS_HOB", " ".join(bblayers)]) def set_machine(self, machine): if machine: self.runCommand(["setVariable", "MACHINE_HOB", machine]) def set_sdk_machine(self, sdk_machine): self.runCommand(["setVariable", "SDKMACHINE_HOB", sdk_machine]) def set_image_fstypes(self, image_fstypes): self.runCommand(["setVariable", "IMAGE_FSTYPES", image_fstypes]) def set_distro(self, distro): self.runCommand(["setVariable", "DISTRO_HOB", distro]) def set_package_format(self, format): package_classes = "" for pkgfmt in format.split(): package_classes += ("package_%s" % pkgfmt + " ") self.runCommand(["setVariable", "PACKAGE_CLASSES_HOB", package_classes]) def set_bbthreads(self, threads): self.runCommand(["setVariable", "BB_NUMBER_THREADS_HOB", threads]) def set_pmake(self, threads): pmake = "-j %s" % threads self.runCommand(["setVariable", "PARALLEL_MAKE_HOB", pmake]) def set_dl_dir(self, directory): self.runCommand(["setVariable", "DL_DIR_HOB", directory]) def set_sstate_dir(self, directory): self.runCommand(["setVariable", "SSTATE_DIR_HOB", directory]) def set_sstate_mirrors(self, url): self.runCommand(["setVariable", "SSTATE_MIRRORS_HOB", url]) def set_extra_size(self, image_extra_size): self.runCommand(["setVariable", "IMAGE_ROOTFS_EXTRA_SPACE", str(image_extra_size)]) def set_rootfs_size(self, image_rootfs_size): self.runCommand(["setVariable", "IMAGE_ROOTFS_SIZE", str(image_rootfs_size)]) def set_incompatible_license(self, incompat_license): self.runCommand(["setVariable", "INCOMPATIBLE_LICENSE_HOB", incompat_license]) def set_extra_config(self, extra_setting): for key in extra_setting.keys(): value = extra_setting[key] self.runCommand(["setVariable", key, value]) def set_config_filter(self, config_filter): self.runCommand(["setConfFilter", config_filter]) def set_http_proxy(self, http_proxy): self.runCommand(["setVariable", "http_proxy", http_proxy]) def set_https_proxy(self, https_proxy): self.runCommand(["setVariable", "https_proxy", https_proxy]) def set_ftp_proxy(self, ftp_proxy): self.runCommand(["setVariable", "ftp_proxy", ftp_proxy]) def set_git_proxy(self, host, port): self.runCommand(["setVariable", "GIT_PROXY_HOST", host]) self.runCommand(["setVariable", "GIT_PROXY_PORT", port]) def set_cvs_proxy(self, host, port): self.runCommand(["setVariable", "CVS_PROXY_HOST", host]) self.runCommand(["setVariable", "CVS_PROXY_PORT", port]) def request_package_info(self): self.commands_async.append(self.SUB_GENERATE_PKGINFO) self.run_next_command(self.POPULATE_PACKAGEINFO) def trigger_sanity_check(self): self.commands_async.append(self.SUB_SANITY_CHECK) self.run_next_command(self.SANITY_CHECK) def trigger_network_test(self): self.commands_async.append(self.SUB_NETWORK_TEST) self.run_next_command(self.NETWORK_TEST) def generate_configuration(self): self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_PATH_LAYERS) self.commands_async.append(self.SUB_FILES_DISTRO) self.commands_async.append(self.SUB_FILES_MACH) self.commands_async.append(self.SUB_FILES_SDKMACH) self.commands_async.append(self.SUB_MATCH_CLASS) self.run_next_command(self.GENERATE_CONFIGURATION) def generate_recipes(self): self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_GNERATE_TGTS) self.run_next_command(self.GENERATE_RECIPES) def generate_packages(self, tgts, default_task="build"): targets = [] targets.extend(tgts) self.recipe_queue = targets self.default_task = default_task self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_BUILD_RECIPES) self.run_next_command(self.GENERATE_PACKAGES) def generate_image(self, image, base_image, toolchain, image_packages=[], toolchain_packages=[], default_task="build"): self.image = image self.base_image = base_image self.toolchain = toolchain self.package_queue = image_packages self.toolchain_packages = toolchain_packages self.default_task = default_task self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_BUILD_IMAGE) self.run_next_command(self.GENERATE_IMAGE) def build_succeeded_async(self): self.building = False def build_failed_async(self): self.initcmd = None self.commands_async = [] self.building = False def cancel_parse(self): self.runCommand(["stateStop"]) def cancel_build(self, force=False): if force: # Force the cooker to stop as quickly as possible self.runCommand(["stateStop"]) else: # Wait for tasks to complete before shutting down, this helps # leave the workdir in a usable state self.runCommand(["stateShutdown"]) def reset_build(self): self.build.reset() def get_logfile(self): return self.server.runCommand(["getVariable", "BB_CONSOLELOG"])[0] def _remove_redundant(self, string): ret = [] for i in string.split(): if i not in ret: ret.append(i) return " ".join(ret) def get_parameters(self): # retrieve the parameters from bitbake params = {} params["core_base"] = self.runCommand(["getVariable", "COREBASE"]) or "" hob_layer = params["core_base"] + "/meta-hob" params["layer"] = self.runCommand(["getVariable", "BBLAYERS"]) or "" params["layers_non_removable"] = self.runCommand(["getVariable", "BBLAYERS_NON_REMOVABLE"]) or "" if hob_layer not in params["layer"].split(): params["layer"] += (" " + hob_layer) if hob_layer not in params["layers_non_removable"].split(): params["layers_non_removable"] += (" " + hob_layer) params["dldir"] = self.runCommand(["getVariable", "DL_DIR"]) or "" params["machine"] = self.runCommand(["getVariable", "MACHINE"]) or "" params["distro"] = self.runCommand(["getVariable", "DISTRO"]) or "defaultsetup" params["pclass"] = self.runCommand(["getVariable", "PACKAGE_CLASSES"]) or "" params["sstatedir"] = self.runCommand(["getVariable", "SSTATE_DIR"]) or "" params["sstatemirror"] = self.runCommand(["getVariable", "SSTATE_MIRRORS"]) or "" num_threads = self.runCommand(["getCpuCount"]) if not num_threads: num_threads = 1 max_threads = 65536 else: try: num_threads = int(num_threads) max_threads = 16 * num_threads except: num_threads = 1 max_threads = 65536 params["max_threads"] = max_threads bbthread = self.runCommand(["getVariable", "BB_NUMBER_THREADS"]) if not bbthread: bbthread = num_threads else: try: bbthread = int(bbthread) except: bbthread = num_threads params["bbthread"] = bbthread pmake = self.runCommand(["getVariable", "PARALLEL_MAKE"]) if not pmake: pmake = num_threads elif isinstance(pmake, int): pass else: try: pmake = int(pmake.lstrip("-j ")) except: pmake = num_threads params["pmake"] = "-j %s" % pmake params["image_addr"] = self.runCommand(["getVariable", "DEPLOY_DIR_IMAGE"]) or "" image_extra_size = self.runCommand(["getVariable", "IMAGE_ROOTFS_EXTRA_SPACE"]) if not image_extra_size: image_extra_size = 0 else: try: image_extra_size = int(image_extra_size) except: image_extra_size = 0 params["image_extra_size"] = image_extra_size image_rootfs_size = self.runCommand(["getVariable", "IMAGE_ROOTFS_SIZE"]) if not image_rootfs_size: image_rootfs_size = 0 else: try: image_rootfs_size = int(image_rootfs_size) except: image_rootfs_size = 0 params["image_rootfs_size"] = image_rootfs_size image_overhead_factor = self.runCommand(["getVariable", "IMAGE_OVERHEAD_FACTOR"]) if not image_overhead_factor: image_overhead_factor = 1 else: try: image_overhead_factor = float(image_overhead_factor) except: image_overhead_factor = 1 params['image_overhead_factor'] = image_overhead_factor params["incompat_license"] = self._remove_redundant(self.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) or "") params["sdk_machine"] = self.runCommand(["getVariable", "SDKMACHINE"]) or self.runCommand(["getVariable", "SDK_ARCH"]) or "" params["image_fstypes"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_FSTYPES"]) or "") params["image_types"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_TYPES"]) or "") params["conf_version"] = self.runCommand(["getVariable", "CONF_VERSION"]) or "" params["lconf_version"] = self.runCommand(["getVariable", "LCONF_VERSION"]) or "" params["runnable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_IMAGE_TYPES"]) or "") params["runnable_machine_patterns"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_MACHINE_PATTERNS"]) or "") params["deployable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "DEPLOYABLE_IMAGE_TYPES"]) or "") params["kernel_image_type"] = self.runCommand(["getVariable", "KERNEL_IMAGETYPE"]) or "" params["tmpdir"] = self.runCommand(["getVariable", "TMPDIR"]) or "" params["distro_version"] = self.runCommand(["getVariable", "DISTRO_VERSION"]) or "" params["target_os"] = self.runCommand(["getVariable", "TARGET_OS"]) or "" params["target_arch"] = self.runCommand(["getVariable", "TARGET_ARCH"]) or "" params["tune_pkgarch"] = self.runCommand(["getVariable", "TUNE_PKGARCH"]) or "" params["bb_version"] = self.runCommand(["getVariable", "BB_MIN_VERSION"]) or "" params["default_task"] = self.runCommand(["getVariable", "BB_DEFAULT_TASK"]) or "build" params["git_proxy_host"] = self.runCommand(["getVariable", "GIT_PROXY_HOST"]) or "" params["git_proxy_port"] = self.runCommand(["getVariable", "GIT_PROXY_PORT"]) or "" params["http_proxy"] = self.runCommand(["getVariable", "http_proxy"]) or "" params["ftp_proxy"] = self.runCommand(["getVariable", "ftp_proxy"]) or "" params["https_proxy"] = self.runCommand(["getVariable", "https_proxy"]) or "" params["cvs_proxy_host"] = self.runCommand(["getVariable", "CVS_PROXY_HOST"]) or "" params["cvs_proxy_port"] = self.runCommand(["getVariable", "CVS_PROXY_PORT"]) or "" params["image_white_pattern"] = self.runCommand(["getVariable", "BBUI_IMAGE_WHITE_PATTERN"]) or "" params["image_black_pattern"] = self.runCommand(["getVariable", "BBUI_IMAGE_BLACK_PATTERN"]) or "" return params
class HobHandler(gobject.GObject): """ This object does BitBake event handling for the hob gui. """ __gsignals__ = { "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, )), "parsing-warning": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, )), "sanity-failed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_INT)), "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, )), "recipe-populated": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), "package-populated": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), "network-passed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), "network-failed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } (GENERATE_CONFIGURATION, GENERATE_RECIPES, GENERATE_PACKAGES, GENERATE_IMAGE, POPULATE_PACKAGEINFO, SANITY_CHECK, NETWORK_TEST) = range(7) (SUB_PATH_LAYERS, SUB_FILES_DISTRO, SUB_FILES_MACH, SUB_FILES_SDKMACH, SUB_MATCH_CLASS, SUB_PARSE_CONFIG, SUB_SANITY_CHECK, SUB_GNERATE_TGTS, SUB_GENERATE_PKGINFO, SUB_BUILD_RECIPES, SUB_BUILD_IMAGE, SUB_NETWORK_TEST) = range(12) def __init__(self, server, recipe_model, package_model): super(HobHandler, self).__init__() self.build = RunningBuild(sequential=True) self.recipe_model = recipe_model self.package_model = package_model self.commands_async = [] self.generating = False self.current_phase = None self.building = False self.recipe_queue = [] self.package_queue = [] self.server = server self.error_msg = "" self.lastCommand = "" self.initcmd = None self.parsing = False def set_busy(self): if not self.generating: self.emit("generating-data") self.generating = True def clear_busy(self): if self.generating: self.emit("data-generated") self.generating = False def runCommand(self, commandline): self.lastCommand = commandline[0] try: result, error = self.server.runCommand(commandline) if error: raise Exception("Error running command '%s': %s" % (commandline, error)) return result except Exception as e: self.commands_async = [] self.clear_busy() self.emit("command-failed", "Hob Exception - %s" % (str(e))) return None 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.SUB_PATH_LAYERS: self.runCommand(["findConfigFilePath", "bblayers.conf"]) elif next_command == self.SUB_FILES_DISTRO: self.runCommand(["findConfigFiles", "DISTRO"]) elif next_command == self.SUB_FILES_MACH: self.runCommand(["findConfigFiles", "MACHINE"]) elif next_command == self.SUB_FILES_SDKMACH: self.runCommand(["findConfigFiles", "MACHINE-SDK"]) elif next_command == self.SUB_MATCH_CLASS: self.runCommand(["findFilesMatchingInDir", "rootfs_", "classes"]) elif next_command == self.SUB_PARSE_CONFIG: self.runCommand(["resetCooker"]) elif next_command == self.SUB_GNERATE_TGTS: self.runCommand( ["generateTargetsTree", "classes/image.bbclass", []]) elif next_command == self.SUB_GENERATE_PKGINFO: self.runCommand(["triggerEvent", "bb.event.RequestPackageInfo()"]) elif next_command == self.SUB_SANITY_CHECK: self.runCommand(["triggerEvent", "bb.event.SanityCheck()"]) elif next_command == self.SUB_NETWORK_TEST: self.runCommand(["triggerEvent", "bb.event.NetworkTest()"]) elif next_command == self.SUB_BUILD_RECIPES: self.clear_busy() self.building = True self.runCommand( ["buildTargets", self.recipe_queue, self.default_task]) self.recipe_queue = [] elif next_command == self.SUB_BUILD_IMAGE: self.clear_busy() self.building = True targets = [self.image] if self.toolchain_packages: self.set_var_in_file("TOOLCHAIN_TARGET_TASK", " ".join(self.toolchain_packages), "local.conf") targets.append(self.toolchain) if targets[0] == "hob-image": self.set_var_in_file("LINGUAS_INSTALL", "", "local.conf") hobImage = self.runCommand(["matchFile", "hob-image.bb"]) if self.base_image != "Start with an empty image recipe": baseImage = self.runCommand( ["matchFile", self.base_image + ".bb"]) version = self.runCommand([ "generateNewImage", hobImage, baseImage, self.package_queue, True, "" ]) targets[0] += version self.recipe_model.set_custom_image_version(version) self.runCommand(["buildTargets", targets, self.default_task]) def display_error(self): self.clear_busy() self.emit("command-failed", self.error_msg) self.error_msg = "" if self.building: self.building = False def handle_event(self, event): if not event: return if self.building: self.current_phase = "building" self.build.handle_event(event) if isinstance(event, bb.event.PackageInfo): self.package_model.populate(event._pkginfolist) self.emit("package-populated") self.run_next_command() elif isinstance(event, bb.event.SanityCheckPassed): reparse = self.runCommand(["getVariable", "BB_INVALIDCONF" ]) or None if reparse is True: self.set_var_in_file("BB_INVALIDCONF", False, "local.conf") self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""]) self.commands_async.prepend(self.SUB_PARSE_CONFIG) self.run_next_command() elif isinstance(event, bb.event.SanityCheckFailed): self.emit("sanity-failed", event._msg, event._network_error) elif isinstance(event, logging.LogRecord): if not self.building: if event.levelno >= logging.ERROR: formatter = bb.msg.BBLogFormatter() msg = formatter.format(event) self.error_msg += msg + '\n' elif event.levelno >= logging.WARNING and self.parsing == True: formatter = bb.msg.BBLogFormatter() msg = formatter.format(event) warn_msg = msg + '\n' self.emit("parsing-warning", warn_msg) elif isinstance(event, bb.event.TargetsTreeGenerated): self.current_phase = "data generation" if event._model: self.recipe_model.populate(event._model) self.emit("recipe-populated") elif isinstance(event, bb.event.ConfigFilesFound): self.current_phase = "configuration lookup" var = event._variable values = event._values values.sort() self.emit("config-updated", var, values) elif isinstance(event, bb.event.ConfigFilePathFound): self.current_phase = "configuration lookup" elif isinstance(event, bb.event.FilesMatchingFound): self.current_phase = "configuration lookup" # FIXME: hard coding, should at least be a variable shared between # here and the caller if event._pattern == "rootfs_": formats = [] for match in event._matches: classname, sep, cls = match.rpartition(".") fs, sep, format = classname.rpartition("_") formats.append(format) formats.sort() self.emit("package-formats-updated", formats) elif isinstance(event, bb.command.CommandCompleted): self.current_phase = None self.run_next_command() elif isinstance(event, bb.command.CommandFailed): if self.error_msg == "": self.error_msg = "The command \"" + self.lastCommand self.error_msg += "\" was sent to bitbake server but it failed. Please" self.error_msg += " check the code executed by this command in bitbake." self.commands_async = [] self.display_error() 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) if isinstance(event, bb.event.ParseStarted): self.parsing = True 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) if isinstance(event, bb.event.ParseCompleted): self.parsing = False elif isinstance(event, bb.event.NetworkTestFailed): self.emit("network-failed") self.run_next_command() elif isinstance(event, bb.event.NetworkTestPassed): self.emit("network-passed") self.run_next_command() if self.error_msg and not self.commands_async: self.display_error() return def init_cooker(self): self.runCommand(["createConfigFile", ".hob.conf"]) def set_extra_inherit(self, bbclass): self.append_var_in_file("INHERIT", bbclass, ".hob.conf") def set_bblayers(self, bblayers): self.set_var_in_file("BBLAYERS", " ".join(bblayers), "bblayers.conf") def set_machine(self, machine): if machine: self.early_assign_var_in_file("MACHINE", machine, "local.conf") def set_sdk_machine(self, sdk_machine): self.set_var_in_file("SDKMACHINE", sdk_machine, "local.conf") def set_image_fstypes(self, image_fstypes): self.set_var_in_file("IMAGE_FSTYPES", image_fstypes, "local.conf") def set_distro(self, distro): self.set_var_in_file("DISTRO", distro, "local.conf") def set_package_format(self, format): package_classes = "" for pkgfmt in format.split(): package_classes += ("package_%s" % pkgfmt + " ") self.set_var_in_file("PACKAGE_CLASSES", package_classes, "local.conf") def set_bbthreads(self, threads): self.set_var_in_file("BB_NUMBER_THREADS", threads, "local.conf") def set_pmake(self, threads): pmake = "-j %s" % threads self.set_var_in_file("PARALLEL_MAKE", pmake, "local.conf") def set_dl_dir(self, directory): self.set_var_in_file("DL_DIR", directory, "local.conf") def set_sstate_dir(self, directory): self.set_var_in_file("SSTATE_DIR", directory, "local.conf") def set_sstate_mirrors(self, url): self.set_var_in_file("SSTATE_MIRRORS", url, "local.conf") def set_extra_size(self, image_extra_size): self.set_var_in_file("IMAGE_ROOTFS_EXTRA_SPACE", str(image_extra_size), "local.conf") def set_rootfs_size(self, image_rootfs_size): self.set_var_in_file("IMAGE_ROOTFS_SIZE", str(image_rootfs_size), "local.conf") def set_incompatible_license(self, incompat_license): self.set_var_in_file("INCOMPATIBLE_LICENSE", incompat_license, "local.conf") def set_extra_setting(self, extra_setting): self.set_var_in_file("EXTRA_SETTING", extra_setting, "local.conf") def set_extra_config(self, extra_setting): old_extra_setting = ast.literal_eval( self.runCommand(["getVariable", "EXTRA_SETTING"]) or "{}") if extra_setting: self.set_var_in_file("EXTRA_SETTING", extra_setting, "local.conf") else: self.remove_var_from_file("EXTRA_SETTING") #remove not needed settings from conf for key in old_extra_setting: if key not in extra_setting: self.remove_var_from_file(key) for key in extra_setting.keys(): value = extra_setting[key] self.set_var_in_file(key, value, "local.conf") def set_http_proxy(self, http_proxy): self.set_var_in_file("http_proxy", http_proxy, "local.conf") def set_https_proxy(self, https_proxy): self.set_var_in_file("https_proxy", https_proxy, "local.conf") def set_ftp_proxy(self, ftp_proxy): self.set_var_in_file("ftp_proxy", ftp_proxy, "local.conf") def set_socks_proxy(self, socks_proxy): self.set_var_in_file("all_proxy", socks_proxy, "local.conf") def set_cvs_proxy(self, host, port): self.set_var_in_file("CVS_PROXY_HOST", host, "local.conf") self.set_var_in_file("CVS_PROXY_PORT", port, "local.conf") def request_package_info(self): self.commands_async.append(self.SUB_GENERATE_PKGINFO) self.run_next_command(self.POPULATE_PACKAGEINFO) def trigger_sanity_check(self): self.commands_async.append(self.SUB_SANITY_CHECK) self.run_next_command(self.SANITY_CHECK) def trigger_network_test(self): self.commands_async.append(self.SUB_NETWORK_TEST) self.run_next_command(self.NETWORK_TEST) def generate_configuration(self): self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""]) self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_PATH_LAYERS) self.commands_async.append(self.SUB_FILES_DISTRO) self.commands_async.append(self.SUB_FILES_MACH) self.commands_async.append(self.SUB_FILES_SDKMACH) self.commands_async.append(self.SUB_MATCH_CLASS) self.run_next_command(self.GENERATE_CONFIGURATION) def generate_recipes(self): self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""]) self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_GNERATE_TGTS) self.run_next_command(self.GENERATE_RECIPES) def generate_packages(self, tgts, default_task="build"): targets = [] targets.extend(tgts) self.recipe_queue = targets self.default_task = default_task self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""]) self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_BUILD_RECIPES) self.run_next_command(self.GENERATE_PACKAGES) def generate_image(self, image, base_image, toolchain, image_packages=[], toolchain_packages=[], default_task="build"): self.image = image self.base_image = base_image self.toolchain = toolchain self.package_queue = image_packages self.toolchain_packages = toolchain_packages self.default_task = default_task self.runCommand(["setPrePostConfFiles", "conf/.hob.conf", ""]) self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_BUILD_IMAGE) self.run_next_command(self.GENERATE_IMAGE) def generate_new_image(self, image, base_image, package_queue, description): base_image = self.runCommand(["matchFile", self.base_image + ".bb"]) self.runCommand([ "generateNewImage", image, base_image, package_queue, False, description ]) def ensure_dir(self, directory): self.runCommand(["ensureDir", directory]) def build_succeeded_async(self): self.building = False def build_failed_async(self): self.initcmd = None self.commands_async = [] self.building = False def cancel_parse(self): self.runCommand(["stateForceShutdown"]) def cancel_build(self, force=False): if force: # Force the cooker to stop as quickly as possible self.runCommand(["stateForceShutdown"]) else: # Wait for tasks to complete before shutting down, this helps # leave the workdir in a usable state self.runCommand(["stateShutdown"]) def reset_build(self): self.build.reset() def get_logfile(self): return self.server.runCommand(["getVariable", "BB_CONSOLELOG"])[0] def get_topdir(self): return self.runCommand(["getVariable", "TOPDIR"]) or "" def _remove_redundant(self, string): ret = [] for i in string.split(): if i not in ret: ret.append(i) return " ".join(ret) def set_var_in_file(self, var, val, default_file=None): self.runCommand(["enableDataTracking"]) self.server.runCommand(["setVarFile", var, val, default_file, "set"]) self.runCommand(["disableDataTracking"]) def early_assign_var_in_file(self, var, val, default_file=None): self.runCommand(["enableDataTracking"]) self.server.runCommand( ["setVarFile", var, val, default_file, "earlyAssign"]) self.runCommand(["disableDataTracking"]) def remove_var_from_file(self, var): self.server.runCommand(["removeVarFile", var]) def append_var_in_file(self, var, val, default_file=None): self.server.runCommand( ["setVarFile", var, val, default_file, "append"]) def append_to_bbfiles(self, val): bbfiles = self.runCommand(["getVariable", "BBFILES", "False"]) or "" bbfiles = bbfiles.split() if val not in bbfiles: self.append_var_in_file("BBFILES", val, "local.conf") def get_parameters(self): # retrieve the parameters from bitbake params = {} params["core_base"] = self.runCommand(["getVariable", "COREBASE" ]) or "" hob_layer = params["core_base"] + "/meta-hob" params["layer"] = self.runCommand(["getVariable", "BBLAYERS"]) or "" params["layers_non_removable"] = self.runCommand( ["getVariable", "BBLAYERS_NON_REMOVABLE"]) or "" if hob_layer not in params["layer"].split(): params["layer"] += (" " + hob_layer) if hob_layer not in params["layers_non_removable"].split(): params["layers_non_removable"] += (" " + hob_layer) params["dldir"] = self.runCommand(["getVariable", "DL_DIR"]) or "" params["machine"] = self.runCommand(["getVariable", "MACHINE"]) or "" params["distro"] = self.runCommand(["getVariable", "DISTRO" ]) or "defaultsetup" params["pclass"] = self.runCommand(["getVariable", "PACKAGE_CLASSES" ]) or "" params["sstatedir"] = self.runCommand(["getVariable", "SSTATE_DIR" ]) or "" params["sstatemirror"] = self.runCommand( ["getVariable", "SSTATE_MIRRORS"]) or "" num_threads = self.runCommand(["getCpuCount"]) if not num_threads: num_threads = 1 max_threads = 65536 else: try: num_threads = int(num_threads) max_threads = 16 * num_threads except: num_threads = 1 max_threads = 65536 params["max_threads"] = max_threads bbthread = self.runCommand(["getVariable", "BB_NUMBER_THREADS"]) if not bbthread: bbthread = num_threads else: try: bbthread = int(bbthread) except: bbthread = num_threads params["bbthread"] = bbthread pmake = self.runCommand(["getVariable", "PARALLEL_MAKE"]) if not pmake: pmake = num_threads elif isinstance(pmake, int): pass else: try: pmake = int(pmake.lstrip("-j ")) except: pmake = num_threads params["pmake"] = "-j %s" % pmake params["image_addr"] = self.runCommand( ["getVariable", "DEPLOY_DIR_IMAGE"]) or "" image_extra_size = self.runCommand( ["getVariable", "IMAGE_ROOTFS_EXTRA_SPACE"]) if not image_extra_size: image_extra_size = 0 else: try: image_extra_size = int(image_extra_size) except: image_extra_size = 0 params["image_extra_size"] = image_extra_size image_rootfs_size = self.runCommand( ["getVariable", "IMAGE_ROOTFS_SIZE"]) if not image_rootfs_size: image_rootfs_size = 0 else: try: image_rootfs_size = int(image_rootfs_size) except: image_rootfs_size = 0 params["image_rootfs_size"] = image_rootfs_size image_overhead_factor = self.runCommand( ["getVariable", "IMAGE_OVERHEAD_FACTOR"]) if not image_overhead_factor: image_overhead_factor = 1 else: try: image_overhead_factor = float(image_overhead_factor) except: image_overhead_factor = 1 params['image_overhead_factor'] = image_overhead_factor params["incompat_license"] = self._remove_redundant( self.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) or "") params["sdk_machine"] = self.runCommand([ "getVariable", "SDKMACHINE" ]) or self.runCommand(["getVariable", "SDK_ARCH"]) or "" params["image_fstypes"] = self._remove_redundant( self.runCommand(["getVariable", "IMAGE_FSTYPES"]) or "") params["image_types"] = self._remove_redundant( self.runCommand(["getVariable", "IMAGE_TYPES"]) or "") params["conf_version"] = self.runCommand( ["getVariable", "CONF_VERSION"]) or "" params["lconf_version"] = self.runCommand( ["getVariable", "LCONF_VERSION"]) or "" params["runnable_image_types"] = self._remove_redundant( self.runCommand(["getVariable", "RUNNABLE_IMAGE_TYPES"]) or "") params["runnable_machine_patterns"] = self._remove_redundant( self.runCommand(["getVariable", "RUNNABLE_MACHINE_PATTERNS"]) or "") params["deployable_image_types"] = self._remove_redundant( self.runCommand(["getVariable", "DEPLOYABLE_IMAGE_TYPES"]) or "") params["kernel_image_type"] = self.runCommand( ["getVariable", "KERNEL_IMAGETYPE"]) or "" params["tmpdir"] = self.runCommand(["getVariable", "TMPDIR"]) or "" params["distro_version"] = self.runCommand( ["getVariable", "DISTRO_VERSION"]) or "" params["target_os"] = self.runCommand(["getVariable", "TARGET_OS" ]) or "" params["target_arch"] = self.runCommand(["getVariable", "TARGET_ARCH" ]) or "" params["tune_pkgarch"] = self.runCommand( ["getVariable", "TUNE_PKGARCH"]) or "" params["bb_version"] = self.runCommand( ["getVariable", "BB_MIN_VERSION"]) or "" params["default_task"] = self.runCommand( ["getVariable", "BB_DEFAULT_TASK"]) or "build" params["socks_proxy"] = self.runCommand(["getVariable", "all_proxy" ]) or "" params["http_proxy"] = self.runCommand(["getVariable", "http_proxy" ]) or "" params["ftp_proxy"] = self.runCommand(["getVariable", "ftp_proxy" ]) or "" params["https_proxy"] = self.runCommand(["getVariable", "https_proxy" ]) or "" params["cvs_proxy_host"] = self.runCommand( ["getVariable", "CVS_PROXY_HOST"]) or "" params["cvs_proxy_port"] = self.runCommand( ["getVariable", "CVS_PROXY_PORT"]) or "" params["image_white_pattern"] = self.runCommand( ["getVariable", "BBUI_IMAGE_WHITE_PATTERN"]) or "" params["image_black_pattern"] = self.runCommand( ["getVariable", "BBUI_IMAGE_BLACK_PATTERN"]) or "" return params
class HobHandler(gobject.GObject): """ This object does BitBake event handling for the hob gui. """ __gsignals__ = { "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_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, server, server_addr, client_addr, recipe_model, package_model): super(HobHandler, self).__init__() self.build = RunningBuild(sequential=True) self.recipe_model = recipe_model self.package_model = package_model self.commands_async = [] self.generating = False self.current_phase = None self.building = False self.recipe_queue = [] self.package_queue = [] self.server = server 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 def clear_busy(self): if self.generating: self.emit("data-generated") self.generating = False def run_next_command(self, initcmd=None): if initcmd: self.initcmd = initcmd if self.commands_async: self.set_busy() next_command = self.commands_async.pop(0) else: self.clear_busy() if self.initcmd: 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 next_command == self.CFG_FILES_DISTRO: self.server.runCommand(["findConfigFiles", "DISTRO"]) elif next_command == self.CFG_FILES_MACH: self.server.runCommand(["findConfigFiles", "MACHINE"]) elif next_command == self.CFG_FILES_SDKMACH: self.server.runCommand(["findConfigFiles", "MACHINE-SDK"]) elif next_command == self.FILES_MATCH_CLASS: self.server.runCommand(["findFilesMatchingInDir", "rootfs_", "classes"]) 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", []]) 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.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 if self.building: self.current_phase = "building" 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.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 values = event._values values.sort() self.emit("config-updated", var, values) elif isinstance(event, bb.event.ConfigFilePathFound): self.current_phase = "configuration lookup" elif isinstance(event, bb.event.FilesMatchingFound): self.current_phase = "configuration lookup" # FIXME: hard coding, should at least be a variable shared between # here and the caller if event._pattern == "rootfs_": formats = [] for match in event._matches: classname, sep, cls = match.rpartition(".") fs, sep, format = classname.rpartition("_") formats.append(format) formats.sort() self.emit("package-formats-updated", formats) elif isinstance(event, bb.command.CommandCompleted): self.current_phase = None self.run_next_command() # TODO: Currently there are NoProvider issues when generate # universe tree dependency for non-x86 architecture. # Comment the follow code to enable the build of non-x86 # architectures in Hob. #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.commands_async = [] 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 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]) 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): 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", "PARALLEL_MAKE", pmake]) 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]) def set_sstate_mirror(self, url): self.server.runCommand(["setVariable", "SSTATE_MIRROR", url]) def set_extra_size(self, image_extra_size): self.server.runCommand(["setVariable", "IMAGE_ROOTFS_EXTRA_SPACE", str(image_extra_size)]) def set_rootfs_size(self, image_rootfs_size): self.server.runCommand(["setVariable", "IMAGE_ROOTFS_SIZE", str(image_rootfs_size)]) def set_incompatible_license(self, incompat_license): self.server.runCommand(["setVariable", "INCOMPATIBLE_LICENSE", incompat_license]) 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_parse(self): self.server.runCommand(["stateStop"]) def cancel_build(self, force=False): if force: # Force the cooker to stop as quickly as possible self.server.runCommand(["stateStop"]) else: # Wait for tasks to complete before shutting down, this helps # leave the workdir in a usable state self.server.runCommand(["stateShutdown"]) 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_fstypes"] = self.server.runCommand(["getVariable", "IMAGE_FSTYPES"]) or "" # walkaround params["image_types"] = " ".join(hcc.SUPPORTED_IMAGE_TYPES.keys()).lstrip(" ") params["conf_version"] = self.server.runCommand(["getVariable", "CONF_VERSION"]) or "" params["lconf_version"] = self.server.runCommand(["getVariable", "LCONF_VERSION"]) or "" return params
class HobHandler(gobject.GObject): """ This object does BitBake event handling for the hob gui. """ __gsignals__ = { "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, )), } (GENERATE_CONFIGURATION, GENERATE_RECIPES, GENERATE_PACKAGES, GENERATE_IMAGE, POPULATE_PACKAGEINFO, SANITY_CHECK) = range(6) (SUB_PATH_LAYERS, SUB_FILES_DISTRO, SUB_FILES_MACH, SUB_FILES_SDKMACH, SUB_MATCH_CLASS, SUB_PARSE_CONFIG, SUB_SANITY_CHECK, SUB_GNERATE_TGTS, SUB_GENERATE_PKGINFO, SUB_BUILD_RECIPES, SUB_BUILD_IMAGE) = range(11) def __init__(self, server, recipe_model, package_model): super(HobHandler, self).__init__() self.build = RunningBuild(sequential=True) self.recipe_model = recipe_model self.package_model = package_model self.commands_async = [] self.generating = False self.current_phase = None self.building = False self.recipe_queue = [] self.package_queue = [] self.server = server self.error_msg = "" self.initcmd = None def set_busy(self): if not self.generating: self.emit("generating-data") self.generating = True def clear_busy(self): if self.generating: self.emit("data-generated") self.generating = False def runCommand(self, commandline): try: return self.server.runCommand(commandline) except Exception as e: self.commands_async = [] self.clear_busy() self.emit("command-failed", "Hob Exception - %s" % (str(e))) return None 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.SUB_PATH_LAYERS: self.runCommand(["findConfigFilePath", "bblayers.conf"]) elif next_command == self.SUB_FILES_DISTRO: self.runCommand(["findConfigFiles", "DISTRO"]) elif next_command == self.SUB_FILES_MACH: self.runCommand(["findConfigFiles", "MACHINE"]) elif next_command == self.SUB_FILES_SDKMACH: self.runCommand(["findConfigFiles", "MACHINE-SDK"]) elif next_command == self.SUB_MATCH_CLASS: self.runCommand(["findFilesMatchingInDir", "rootfs_", "classes"]) elif next_command == self.SUB_PARSE_CONFIG: self.runCommand(["parseConfigurationFiles", "", ""]) elif next_command == self.SUB_GNERATE_TGTS: self.runCommand( ["generateTargetsTree", "classes/image.bbclass", []]) elif next_command == self.SUB_GENERATE_PKGINFO: self.runCommand(["triggerEvent", "bb.event.RequestPackageInfo()"]) elif next_command == self.SUB_SANITY_CHECK: self.runCommand(["triggerEvent", "bb.event.SanityCheck()"]) elif next_command == self.SUB_BUILD_RECIPES: self.clear_busy() self.building = True self.runCommand( ["buildTargets", self.recipe_queue, self.default_task]) self.recipe_queue = [] elif next_command == self.SUB_BUILD_IMAGE: self.clear_busy() self.building = True targets = [self.image] if self.package_queue: self.runCommand(["setVariable", "LINGUAS_INSTALL", ""]) self.runCommand([ "setVariable", "PACKAGE_INSTALL", " ".join(self.package_queue) ]) if self.toolchain_packages: self.runCommand([ "setVariable", "TOOLCHAIN_TARGET_TASK", " ".join(self.toolchain_packages) ]) targets.append(self.toolchain) self.runCommand(["buildTargets", targets, self.default_task]) def handle_event(self, event): if not event: return if self.building: self.current_phase = "building" self.build.handle_event(event) if isinstance(event, bb.event.PackageInfo): self.package_model.populate(event._pkginfolist) self.run_next_command() elif isinstance(event, bb.event.SanityCheckPassed): 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.recipe_model.populate(event._model) elif isinstance(event, bb.event.ConfigFilesFound): self.current_phase = "configuration lookup" var = event._variable values = event._values values.sort() self.emit("config-updated", var, values) elif isinstance(event, bb.event.ConfigFilePathFound): self.current_phase = "configuration lookup" elif isinstance(event, bb.event.FilesMatchingFound): self.current_phase = "configuration lookup" # FIXME: hard coding, should at least be a variable shared between # here and the caller if event._pattern == "rootfs_": formats = [] for match in event._matches: classname, sep, cls = match.rpartition(".") fs, sep, format = classname.rpartition("_") formats.append(format) formats.sort() self.emit("package-formats-updated", formats) elif isinstance(event, bb.command.CommandCompleted): self.current_phase = None self.run_next_command() elif isinstance(event, bb.command.CommandFailed): self.commands_async = [] self.clear_busy() 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 init_cooker(self): self.runCommand(["initCooker"]) def set_extra_inherit(self, bbclass): inherits = self.runCommand(["getVariable", "INHERIT"]) or "" inherits = inherits + " " + bbclass self.runCommand(["setVariable", "INHERIT", inherits]) def set_bblayers(self, bblayers): self.runCommand(["setVariable", "BBLAYERS_HOB", " ".join(bblayers)]) def set_machine(self, machine): if machine: self.runCommand(["setVariable", "MACHINE_HOB", machine]) def set_sdk_machine(self, sdk_machine): self.runCommand(["setVariable", "SDKMACHINE_HOB", sdk_machine]) def set_image_fstypes(self, image_fstypes): self.runCommand(["setVariable", "IMAGE_FSTYPES", image_fstypes]) def set_distro(self, distro): self.runCommand(["setVariable", "DISTRO_HOB", distro]) def set_package_format(self, format): package_classes = "" for pkgfmt in format.split(): package_classes += ("package_%s" % pkgfmt + " ") self.runCommand( ["setVariable", "PACKAGE_CLASSES_HOB", package_classes]) def set_bbthreads(self, threads): self.runCommand(["setVariable", "BB_NUMBER_THREADS_HOB", threads]) def set_pmake(self, threads): pmake = "-j %s" % threads self.runCommand(["setVariable", "PARALLEL_MAKE_HOB", pmake]) def set_dl_dir(self, directory): self.runCommand(["setVariable", "DL_DIR_HOB", directory]) def set_sstate_dir(self, directory): self.runCommand(["setVariable", "SSTATE_DIR_HOB", directory]) def set_sstate_mirror(self, url): self.runCommand(["setVariable", "SSTATE_MIRROR_HOB", url]) def set_extra_size(self, image_extra_size): self.runCommand( ["setVariable", "IMAGE_ROOTFS_EXTRA_SPACE", str(image_extra_size)]) def set_rootfs_size(self, image_rootfs_size): self.runCommand( ["setVariable", "IMAGE_ROOTFS_SIZE", str(image_rootfs_size)]) def set_incompatible_license(self, incompat_license): self.runCommand( ["setVariable", "INCOMPATIBLE_LICENSE_HOB", incompat_license]) def set_extra_config(self, extra_setting): for key in extra_setting.keys(): value = extra_setting[key] self.runCommand(["setVariable", key, value]) def set_config_filter(self, config_filter): self.runCommand(["setConfFilter", config_filter]) def set_http_proxy(self, http_proxy): self.runCommand(["setVariable", "http_proxy", http_proxy]) def set_https_proxy(self, https_proxy): self.runCommand(["setVariable", "https_proxy", https_proxy]) def set_ftp_proxy(self, ftp_proxy): self.runCommand(["setVariable", "ftp_proxy", ftp_proxy]) def set_all_proxy(self, all_proxy): self.runCommand(["setVariable", "all_proxy", all_proxy]) def set_git_proxy(self, host, port): self.runCommand(["setVariable", "GIT_PROXY_HOST", host]) self.runCommand(["setVariable", "GIT_PROXY_PORT", port]) def set_cvs_proxy(self, host, port): self.runCommand(["setVariable", "CVS_PROXY_HOST", host]) self.runCommand(["setVariable", "CVS_PROXY_PORT", port]) def request_package_info(self): self.commands_async.append(self.SUB_GENERATE_PKGINFO) self.run_next_command(self.POPULATE_PACKAGEINFO) def trigger_sanity_check(self): self.commands_async.append(self.SUB_SANITY_CHECK) self.run_next_command(self.SANITY_CHECK) def generate_configuration(self): self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_PATH_LAYERS) self.commands_async.append(self.SUB_FILES_DISTRO) self.commands_async.append(self.SUB_FILES_MACH) self.commands_async.append(self.SUB_FILES_SDKMACH) self.commands_async.append(self.SUB_MATCH_CLASS) self.run_next_command(self.GENERATE_CONFIGURATION) def generate_recipes(self): self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_GNERATE_TGTS) self.run_next_command(self.GENERATE_RECIPES) def generate_packages(self, tgts, default_task="build"): targets = [] targets.extend(tgts) self.recipe_queue = targets self.default_task = default_task self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_BUILD_RECIPES) self.run_next_command(self.GENERATE_PACKAGES) def generate_image(self, image, toolchain, image_packages=[], toolchain_packages=[], default_task="build"): self.image = image self.toolchain = toolchain self.package_queue = image_packages self.toolchain_packages = toolchain_packages self.default_task = default_task self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_BUILD_IMAGE) self.run_next_command(self.GENERATE_IMAGE) def build_succeeded_async(self): self.building = False def build_failed_async(self): self.initcmd = None self.commands_async = [] self.building = False def cancel_parse(self): self.runCommand(["stateStop"]) def cancel_build(self, force=False): if force: # Force the cooker to stop as quickly as possible self.runCommand(["stateStop"]) else: # Wait for tasks to complete before shutting down, this helps # leave the workdir in a usable state self.runCommand(["stateShutdown"]) def reset_build(self): self.build.reset() def _remove_redundant(self, string): ret = [] for i in string.split(): if i not in ret: ret.append(i) return " ".join(ret) def get_parameters(self): # retrieve the parameters from bitbake params = {} params["core_base"] = self.runCommand(["getVariable", "COREBASE" ]) or "" hob_layer = params["core_base"] + "/meta-hob" params["layer"] = self.runCommand(["getVariable", "BBLAYERS"]) or "" if hob_layer not in params["layer"].split(): params["layer"] += (" " + hob_layer) params["dldir"] = self.runCommand(["getVariable", "DL_DIR"]) or "" params["machine"] = self.runCommand(["getVariable", "MACHINE"]) or "" params["distro"] = self.runCommand(["getVariable", "DISTRO" ]) or "defaultsetup" params["pclass"] = self.runCommand(["getVariable", "PACKAGE_CLASSES" ]) or "" params["sstatedir"] = self.runCommand(["getVariable", "SSTATE_DIR" ]) or "" params["sstatemirror"] = self.runCommand( ["getVariable", "SSTATE_MIRROR"]) or "" num_threads = self.runCommand(["getCpuCount"]) if not num_threads: num_threads = 1 max_threads = 65536 else: try: num_threads = int(num_threads) max_threads = 16 * num_threads except: num_threads = 1 max_threads = 65536 params["max_threads"] = max_threads bbthread = self.runCommand(["getVariable", "BB_NUMBER_THREADS"]) if not bbthread: bbthread = num_threads else: try: bbthread = int(bbthread) except: bbthread = num_threads params["bbthread"] = bbthread pmake = self.runCommand(["getVariable", "PARALLEL_MAKE"]) if not pmake: pmake = num_threads elif isinstance(pmake, int): pass else: try: pmake = int(pmake.lstrip("-j ")) except: pmake = num_threads params["pmake"] = "-j %s" % pmake params["image_addr"] = self.runCommand( ["getVariable", "DEPLOY_DIR_IMAGE"]) or "" image_extra_size = self.runCommand( ["getVariable", "IMAGE_ROOTFS_EXTRA_SPACE"]) if not image_extra_size: image_extra_size = 0 else: try: image_extra_size = int(image_extra_size) except: image_extra_size = 0 params["image_extra_size"] = image_extra_size image_rootfs_size = self.runCommand( ["getVariable", "IMAGE_ROOTFS_SIZE"]) if not image_rootfs_size: image_rootfs_size = 0 else: try: image_rootfs_size = int(image_rootfs_size) except: image_rootfs_size = 0 params["image_rootfs_size"] = image_rootfs_size image_overhead_factor = self.runCommand( ["getVariable", "IMAGE_OVERHEAD_FACTOR"]) if not image_overhead_factor: image_overhead_factor = 1 else: try: image_overhead_factor = float(image_overhead_factor) except: image_overhead_factor = 1 params['image_overhead_factor'] = image_overhead_factor params["incompat_license"] = self._remove_redundant( self.runCommand(["getVariable", "INCOMPATIBLE_LICENSE"]) or "") params["sdk_machine"] = self.runCommand([ "getVariable", "SDKMACHINE" ]) or self.runCommand(["getVariable", "SDK_ARCH"]) or "" params["image_fstypes"] = self._remove_redundant( self.runCommand(["getVariable", "IMAGE_FSTYPES"]) or "") params["image_types"] = self._remove_redundant( self.runCommand(["getVariable", "IMAGE_TYPES"]) or "") params["conf_version"] = self.runCommand( ["getVariable", "CONF_VERSION"]) or "" params["lconf_version"] = self.runCommand( ["getVariable", "LCONF_VERSION"]) or "" params["runnable_image_types"] = self._remove_redundant( self.runCommand(["getVariable", "RUNNABLE_IMAGE_TYPES"]) or "") params["runnable_machine_patterns"] = self._remove_redundant( self.runCommand(["getVariable", "RUNNABLE_MACHINE_PATTERNS"]) or "") params["deployable_image_types"] = self._remove_redundant( self.runCommand(["getVariable", "DEPLOYABLE_IMAGE_TYPES"]) or "") params["tmpdir"] = self.runCommand(["getVariable", "TMPDIR"]) or "" params["distro_version"] = self.runCommand( ["getVariable", "DISTRO_VERSION"]) or "" params["target_os"] = self.runCommand(["getVariable", "TARGET_OS" ]) or "" params["target_arch"] = self.runCommand(["getVariable", "TARGET_ARCH" ]) or "" params["tune_pkgarch"] = self.runCommand( ["getVariable", "TUNE_PKGARCH"]) or "" params["bb_version"] = self.runCommand( ["getVariable", "BB_MIN_VERSION"]) or "" params["default_task"] = self.runCommand( ["getVariable", "BB_DEFAULT_TASK"]) or "build" params["git_proxy_host"] = self.runCommand( ["getVariable", "GIT_PROXY_HOST"]) or "" params["git_proxy_port"] = self.runCommand( ["getVariable", "GIT_PROXY_PORT"]) or "" params["http_proxy"] = self.runCommand(["getVariable", "http_proxy" ]) or "" params["ftp_proxy"] = self.runCommand(["getVariable", "ftp_proxy" ]) or "" params["https_proxy"] = self.runCommand(["getVariable", "https_proxy" ]) or "" params["all_proxy"] = self.runCommand(["getVariable", "all_proxy" ]) or "" params["cvs_proxy_host"] = self.runCommand( ["getVariable", "CVS_PROXY_HOST"]) or "" params["cvs_proxy_port"] = self.runCommand( ["getVariable", "CVS_PROXY_PORT"]) or "" return params
class HobHandler(gobject.GObject): """ This object does BitBake event handling for the hob gui. """ __gsignals__ = { "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,)), "parsing-warning" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), "sanity-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,)), "recipe-populated" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), "package-populated" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } (GENERATE_CONFIGURATION, GENERATE_RECIPES, GENERATE_PACKAGES, GENERATE_IMAGE, POPULATE_PACKAGEINFO, SANITY_CHECK) = range(6) (SUB_FILES_MACH, SUB_PARSE_CONFIG, SUB_SANITY_CHECK, SUB_GNERATE_TGTS, SUB_GENERATE_PKGINFO, SUB_BUILD_RECIPES, SUB_BUILD_IMAGE) = range(7) def __init__(self, server, recipe_model, package_model): super(HobHandler, self).__init__() self.build = RunningBuild(sequential=True) self.recipe_model = recipe_model self.package_model = package_model self.commands_async = [] self.generating = False self.current_phase = None self.building = False self.recipe_queue = [] self.package_queue = [] self.server = server self.error_msg = "" self.initcmd = None self.parsing = False def set_busy(self): if not self.generating: self.emit("generating-data") self.generating = True def clear_busy(self): if self.generating: self.emit("data-generated") self.generating = False def runCommand(self, commandline): try: result, error = self.server.runCommand(commandline) if error: raise Exception("Error running command '%s': %s" % (commandline, error)) return result except Exception as e: self.commands_async = [] self.clear_busy() self.emit("command-failed", "Hob Exception - %s" % (str(e))) return None 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.SUB_FILES_MACH: self.runCommand(["findConfigFiles", "MACHINE"]) elif next_command == self.SUB_PARSE_CONFIG: self.runCommand(["enableDataTracking"]) self.runCommand(["parseConfigurationFiles", "", ""]) self.runCommand(["disableDataTracking"]) elif next_command == self.SUB_GNERATE_TGTS: self.runCommand(["generateTargetsTree", "classes/image.bbclass", []]) elif next_command == self.SUB_GENERATE_PKGINFO: self.runCommand(["triggerEvent", "bb.event.RequestPackageInfo()"]) elif next_command == self.SUB_SANITY_CHECK: self.runCommand(["triggerEvent", "bb.event.SanityCheck()"]) elif next_command == self.SUB_BUILD_RECIPES: self.clear_busy() self.building = True self.runCommand(["buildTargets", self.recipe_queue, self.default_task]) self.recipe_queue = [] elif next_command == self.SUB_BUILD_IMAGE: self.clear_busy() self.building = True target = self.image if target == "hob-image": hobImage = self.runCommand(["matchFile", "hob-image.bb"]) if self.base_image != "Start with an empty image recipe": baseImage = self.runCommand(["matchFile", self.base_image + ".bb"]) version = self.runCommand(["generateNewImage", hobImage, baseImage, self.package_queue, True, ""]) target += version self.recipe_model.set_custom_image_version(version) targets = [target + ":do_rootfs"] targets.append("virtual/kernel:do_deploy") targets.append("virtual/kernel:do_populate_sysroot") targets.append("grub:do_deploy") targets.append("grub-conf:do_deploy") if self.toolchain: targets.append(target + ":do_populate_sdk") self.runCommand(["buildTargets", targets, self.default_task]) def display_error(self): self.clear_busy() self.emit("command-failed", self.error_msg) self.error_msg = "" if self.building: self.building = False def handle_event(self, event): if not event: return if self.building: self.current_phase = "building" self.build.handle_event(event) if isinstance(event, bb.event.PackageInfo): self.package_model.populate(event._pkginfolist) self.emit("package-populated") self.run_next_command() elif isinstance(event, bb.event.SanityCheckPassed): reparse = self.runCommand(["getVariable", "BB_INVALIDCONF"]) or None if reparse is True: self.set_var_in_file("BB_INVALIDCONF", False, "local.conf") self.runCommand(["parseConfigurationFiles", "", ""]) self.run_next_command() elif isinstance(event, bb.event.SanityCheckFailed): self.emit("sanity-failed", event._msg) elif isinstance(event, logging.LogRecord): if not self.building: if event.levelno >= logging.ERROR: formatter = bb.msg.BBLogFormatter() msg = formatter.format(event) self.error_msg += msg + '\n' elif event.levelno >= logging.WARNING and self.parsing == True: formatter = bb.msg.BBLogFormatter() msg = formatter.format(event) warn_msg = msg + '\n' self.emit("parsing-warning", warn_msg) elif isinstance(event, bb.event.TargetsTreeGenerated): self.current_phase = "data generation" if event._model: self.recipe_model.populate(event._model) self.emit("recipe-populated") elif isinstance(event, bb.event.ConfigFilesFound): self.current_phase = "configuration lookup" var = event._variable values = event._values values.sort() self.emit("config-updated", var, values) elif isinstance(event, bb.event.ConfigFilePathFound): self.current_phase = "configuration lookup" elif isinstance(event, bb.event.FilesMatchingFound): self.current_phase = "configuration lookup" # FIXME: hard coding, should at least be a variable shared between # here and the caller if event._pattern == "rootfs_": formats = [] for match in event._matches: classname, sep, cls = match.rpartition(".") fs, sep, format = classname.rpartition("_") formats.append(format) formats.sort() self.emit("package-formats-updated", formats) elif isinstance(event, bb.command.CommandCompleted): self.current_phase = None self.run_next_command() elif isinstance(event, bb.command.CommandFailed): self.commands_async = [] self.display_error() 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) if isinstance(event, bb.event.ParseStarted): self.parsing = True 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) if isinstance(event, bb.event.ParseCompleted): self.parsing = False if self.error_msg and not self.commands_async: self.display_error() return def init_cooker(self): self.runCommand(["initCooker"]) def reset_cooker(self): self.runCommand(["enableDataTracking"]) self.runCommand(["resetCooker"]) self.runCommand(["disableDataTracking"]) def set_distro(self, distro): self.set_var_in_file("DISTRO", distro, "local.conf") def request_package_info(self): self.commands_async.append(self.SUB_GENERATE_PKGINFO) self.run_next_command(self.POPULATE_PACKAGEINFO) def trigger_sanity_check(self): self.commands_async.append(self.SUB_SANITY_CHECK) self.run_next_command(self.SANITY_CHECK) def generate_configuration(self): self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_FILES_MACH) self.run_next_command(self.GENERATE_CONFIGURATION) def generate_recipes(self): self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_GNERATE_TGTS) self.run_next_command(self.GENERATE_RECIPES) def generate_packages(self, tgts, default_task="build"): targets = [] targets.extend(tgts) self.recipe_queue = targets self.default_task = default_task self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_BUILD_RECIPES) self.run_next_command(self.GENERATE_PACKAGES) def generate_image(self, image, base_image, image_packages=[], toolchain=False, default_task="build"): self.image = image self.base_image = base_image self.toolchain = toolchain self.package_queue = image_packages self.default_task = default_task self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_BUILD_IMAGE) self.run_next_command(self.GENERATE_IMAGE) def generate_new_image(self, image, base_image, package_queue, description): base_image = self.runCommand(["matchFile", self.base_image + ".bb"]) self.runCommand(["generateNewImage", image, base_image, package_queue, False, description]) def ensure_dir(self, directory): self.runCommand(["ensureDir", directory]) def build_succeeded_async(self): self.building = False def build_failed_async(self): self.initcmd = None self.commands_async = [] self.building = False def cancel_parse(self): self.runCommand(["stateForceShutdown"]) def cancel_build(self, force=False): if force: # Force the cooker to stop as quickly as possible self.runCommand(["stateForceShutdown"]) else: # Wait for tasks to complete before shutting down, this helps # leave the workdir in a usable state self.runCommand(["stateShutdown"]) def reset_build(self): self.build.reset() def _remove_redundant(self, string): ret = [] for i in string.split(): if i not in ret: ret.append(i) return " ".join(ret) def set_var_in_file(self, var, val, default_file=None): self.runCommand(["enableDataTracking"]) self.runCommand(["setVarFile", var, val, default_file, "set"]) self.runCommand(["disableDataTracking"]) def early_assign_var_in_file(self, var, val, default_file=None): self.runCommand(["setVarFile", var, val, default_file, "earlyAssign"]) self.runCommand(["disableDataTracking"]) def remove_var_from_file(self, var): self.runCommand(["removeVarFile", var]) def append_var_in_file(self, var, val, default_file=None): self.runCommand(["setVarFile", var, val, default_file, "append"]) def append_to_bbfiles(self, val): bbfiles = self.runCommand(["getVariable", "BBFILES", "False"]) or "" bbfiles = bbfiles.split() if val not in bbfiles: self.append_var_in_file("BBFILES", val, "local.conf") def get_parameters(self): # retrieve the parameters from bitbake params = {} params["image_list"] = self.runCommand(["getVariable", "HOB_IMAGELIST"]) or "" params["image_addr"] = self.runCommand(["getVariable", "DEPLOY_DIR_IMAGE"]) or "" params["image_types"] = self._remove_redundant(self.runCommand(["getVariable", "IMAGE_TYPES"]) or "") params["runnable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_IMAGE_TYPES"]) or "") params["runnable_machine_patterns"] = self._remove_redundant(self.runCommand(["getVariable", "RUNNABLE_MACHINE_PATTERNS"]) or "") params["deployable_image_types"] = self._remove_redundant(self.runCommand(["getVariable", "DEPLOYABLE_IMAGE_TYPES"]) or "") params["kernel_image_type"] = self.runCommand(["getVariable", "KERNEL_IMAGETYPE"]) or "" params["core_base"] = self.runCommand(["getVariable", "COREBASE"]) or "" params["staging_dir_native"] = self.runCommand(["getVariable", "STAGING_DIR_NATIVE"]) or "" params["staging_kernel_dir"] = self.runCommand(["getVariable", "STAGING_KERNEL_DIR"]) or "" params["tmpdir"] = self.runCommand(["getVariable", "TMPDIR"]) or "" params["distro"] = self.runCommand(["getVariable", "DISTRO"]) or "" return params
class MainWindow (gtk.Window): def __init__(self, taskmodel, handler, configurator, prefs, layers, mach): gtk.Window.__init__(self) # global state self.curr_mach = mach self.machine_handler_id = None self.image_combo_id = None self.generating = False self.files_to_clean = [] self.selected_image = None self.selected_packages = None self.stopping = False self.model = taskmodel self.model.connect("tasklist-populated", self.update_model) self.model.connect("image-changed", self.image_changed_string_cb) self.handler = handler self.configurator = configurator self.prefs = prefs self.layers = layers self.save_path = None self.dirty = False self.build_succeeded = False self.connect("delete-event", self.destroy_window) self.set_title("Image Creator") self.set_icon_name("applications-development") self.set_default_size(1000, 650) self.build = RunningBuild(sequential=True) self.build.connect("build-failed", self.running_build_failed_cb) self.build.connect("build-succeeded", self.running_build_succeeded_cb) self.build.connect("build-started", self.build_started_cb) self.build.connect("build-complete", self.build_complete_cb) vbox = gtk.VBox(False, 0) vbox.set_border_width(0) vbox.show() self.add(vbox) self.menu = self.create_menu() vbox.pack_start(self.menu, False) createview = self.create_build_gui() self.back = None self.cancel = None buildview = self.view_build_gui() self.nb = gtk.Notebook() self.nb.append_page(createview) self.nb.append_page(buildview) self.nb.set_current_page(0) self.nb.set_show_tabs(False) vbox.pack_start(self.nb, expand=True, fill=True) def destroy_window(self, widget, event): self.quit() def menu_quit(self, action): self.quit() def quit(self): if self.dirty and len(self.model.contents): question = "Would you like to save your customisations?" dialog = CrumbsDialog(self, question, gtk.STOCK_DIALOG_WARNING) dialog.add_buttons(gtk.STOCK_NO, gtk.RESPONSE_NO, gtk.STOCK_YES, gtk.RESPONSE_YES) resp = dialog.run() dialog.destroy() if resp == gtk.RESPONSE_YES: if not self.save_path: self.get_save_path() if self.save_path: self.save_recipe_file() rep = self.model.get_build_rep() rep.writeRecipe(self.save_path, self.model) # Prevent the busy cursor being shown after hob exits if quit is called # whilst the busy cursor is set self.set_busy_cursor(False) self.handler.remove_temp_dir() gtk.main_quit() """ In the case of a fatal error give the user as much information as possible and then exit. """ def fatal_error_cb(self, handler, errormsg, phase): lbl = "<b>Error!</b>\nThere was an unrecoverable error during the" lbl = lbl + " <i>%s</i> phase of BitBake. This must be" % phase lbl = lbl + " rectified before the GUI will function. The error" lbl = lbl + " message which which caused this is:\n\n\"%s\"" % errormsg dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_ERROR) dialog.add_button("Exit", gtk.RESPONSE_OK) response = dialog.run() dialog.destroy() self.set_busy_cursor(False) gtk.main_quit() def scroll_tv_cb(self, model, path, it, view): view.scroll_to_cell(path) def running_build_succeeded_cb(self, running_build): self.build_succeeded = True def running_build_failed_cb(self, running_build): self.build_succeeded = False def image_changed_string_cb(self, model, new_image): self.selected_image = new_image # disconnect the image combo's signal handler if self.image_combo_id: self.image_combo.disconnect(self.image_combo_id) self.image_combo_id = None cnt = 0 it = self.model.images.get_iter_first() while it: path = self.model.images.get_path(it) if self.model.images[path][self.model.COL_NAME] == new_image: self.image_combo.set_active(cnt) break it = self.model.images.iter_next(it) cnt = cnt + 1 # Reconnect the signal handler if not self.image_combo_id: self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) def image_changed_cb(self, combo): model = self.image_combo.get_model() it = self.image_combo.get_active_iter() if it: path = model.get_path(it) # Firstly, deselect the previous image userp, _ = self.model.get_selected_packages() self.model.reset() # Now select the new image and save its path in case we # change the image later self.toggle_package(path, model, image=True) if len(userp): self.model.set_selected_packages(userp) self.selected_image = model[path][self.model.COL_NAME] def reload_triggered_cb(self, handler, image, packages): if image: self.selected_image = image if len(packages): self.selected_packages = packages.split() def data_generated(self, handler): self.generating = False self.enable_widgets() def machine_combo_changed_cb(self, combo, handler): mach = combo.get_active_text() if mach != self.curr_mach: self.curr_mach = mach # Flush this straight to the file as MACHINE is changed # independently of other 'Preferences' self.configurator.setConfVar('MACHINE', mach) self.configurator.writeConf() handler.set_machine(mach) handler.reload_data() def update_machines(self, handler, machines): active = 0 # disconnect the signal handler before updating the combo model if self.machine_handler_id: self.machine_combo.disconnect(self.machine_handler_id) self.machine_handler_id = None model = self.machine_combo.get_model() if model: model.clear() for machine in machines: self.machine_combo.append_text(machine) if machine == self.curr_mach: self.machine_combo.set_active(active) active = active + 1 self.machine_handler_id = self.machine_combo.connect("changed", self.machine_combo_changed_cb, handler) def set_busy_cursor(self, busy=True): """ Convenience method to set the cursor to a spinner when executing a potentially lengthy process. A busy value of False will set the cursor back to the default left pointer. """ if busy: cursor = gtk.gdk.Cursor(gtk.gdk.WATCH) else: # TODO: presumably the default cursor is different on RTL # systems. Can we determine the default cursor? Or at least # the cursor which is set before we change it? cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) window = self.get_root_window() window.set_cursor(cursor) def busy_idle_func(self): if self.generating: self.progress.pulse() return True else: if not self.image_combo_id: self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) self.progress.set_text("Loaded") self.progress.set_fraction(0.0) self.set_busy_cursor(False) return False def busy(self, handler): self.generating = True self.progress.set_text("Loading...") self.set_busy_cursor() if self.image_combo_id: self.image_combo.disconnect(self.image_combo_id) self.image_combo_id = None self.progress.pulse() gobject.timeout_add (100, self.busy_idle_func) self.disable_widgets() def enable_widgets(self): self.menu.set_sensitive(True) self.machine_combo.set_sensitive(True) self.image_combo.set_sensitive(True) self.nb.set_sensitive(True) self.contents_tree.set_sensitive(True) def disable_widgets(self): self.menu.set_sensitive(False) self.machine_combo.set_sensitive(False) self.image_combo.set_sensitive(False) self.nb.set_sensitive(False) self.contents_tree.set_sensitive(False) def update_model(self, model): # We want the packages model to be alphabetised and sortable so create # a TreeModelSort to use in the view pkgsaz_model = gtk.TreeModelSort(self.model.packages_model()) pkgsaz_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) # Unset default sort func so that we only toggle between A-Z and # Z-A sorting pkgsaz_model.set_default_sort_func(None) self.pkgsaz_tree.set_model(pkgsaz_model) self.image_combo.set_model(self.model.images_model()) # Without this the image combo is incorrectly sized on first load of the GUI self.image_combo.set_active(0) self.image_combo.set_active(-1) if not self.image_combo_id: self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) # We want the contents to be alphabetised so create a TreeModelSort to # use in the view contents_model = gtk.TreeModelSort(self.model.contents_model()) contents_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) # Unset default sort func so that we only toggle between A-Z and # Z-A sorting contents_model.set_default_sort_func(None) self.contents_tree.set_model(contents_model) self.tasks_tree.set_model(self.model.tasks_model()) if self.selected_image: if self.image_combo_id: self.image_combo.disconnect(self.image_combo_id) self.image_combo_id = None self.model.set_selected_image(self.selected_image) if not self.image_combo_id: self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) if self.selected_packages: self.model.set_selected_packages(self.selected_packages) def reset_clicked_cb(self, button): lbl = "<b>Reset your selections?</b>\n\nAny new changes you have made will be lost" dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button("Reset", gtk.RESPONSE_OK) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_OK: self.reset_build() self.search.set_text("") self.selected_image = None return def reset_build(self): if self.image_combo_id: self.image_combo.disconnect(self.image_combo_id) self.image_combo_id = None self.image_combo.set_active(-1) self.model.reset() if not self.image_combo_id: self.image_combo_id = self.image_combo.connect("changed", self.image_changed_cb) def layers_cb(self, action): resp = self.layers.run() self.layers.save_current_layers() self.layers.hide() def add_layer_cb(self, action): self.layers.find_layer(self) self.layers.save_current_layers() def preferences_cb(self, action): resp = self.prefs.run() self.prefs.write_changes() self.prefs.hide() def about_cb(self, action): about = gtk.AboutDialog() about.set_name("Image Creator") about.set_copyright("Copyright (C) 2011 Intel Corporation") about.set_authors(["Joshua Lock <*****@*****.**>"]) about.set_logo_icon_name("applications-development") about.run() about.destroy() def save_recipe_file(self): rep = self.model.get_build_rep() rep.writeRecipe(self.save_path, self.model) self.dirty = False def get_save_path(self): chooser = gtk.FileChooserDialog(title=None, parent=self, action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK,)) chooser.set_current_name("myimage.bb") response = chooser.run() if response == gtk.RESPONSE_OK: save_path = chooser.get_filename() else: save_path = None chooser.destroy() self.save_path = save_path def save_cb(self, action): if not self.save_path: self.get_save_path() if self.save_path: self.save_recipe_file() def save_as_cb(self, action): self.get_save_path() if self.save_path: self.save_recipe_file() def open_cb(self, action): chooser = gtk.FileChooserDialog(title=None, parent=self, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) response = chooser.run() rep = BuildRep(None, None, None) recipe = chooser.get_filename() if response == gtk.RESPONSE_OK: rep.loadRecipe(recipe) self.save_path = recipe self.model.load_image_rep(rep) self.dirty = False chooser.destroy() def bake_clicked_cb(self, button): build_image = True rep = self.model.get_build_rep() # If no base image and no user selected packages don't build anything if not self.selected_image and not len(rep.userpkgs): lbl = "<b>No selections made</b>\nYou have not made any selections" lbl = lbl + " so there isn't anything to bake at this time." dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) dialog.run() dialog.destroy() return # Else if no base image, ask whether to just build packages or whether # to build a rootfs with the selected packages in elif not self.selected_image: lbl = "<b>Build empty image or only packages?</b>\nA base image" lbl = lbl + " has not been selected.\n\'Empty image' will build" lbl = lbl + " an image with only the selected packages as its" lbl = lbl + " contents.\n'Packages Only' will build only the" lbl = lbl + " selected packages, no image will be created" dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button("Empty Image", gtk.RESPONSE_OK) dialog.add_button("Packages Only", gtk.RESPONSE_YES) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_CANCEL: return elif response == gtk.RESPONSE_YES: build_image = False elif response == gtk.RESPONSE_OK: rep.base_image = "empty" # Ensure at least one value is set in IMAGE_FSTYPES. have_selected_fstype = False if (len(self.prefs.selected_image_types) and len(self.prefs.selected_image_types[0])): have_selected_fstype = True if build_image and not have_selected_fstype: lbl = "<b>No image output type selected</b>\nThere is no image output" lbl = lbl + " selected for the build. Please set an output image type" lbl = lbl + " in the preferences (Edit -> Preferences)." dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) dialog.run() dialog.destroy() return elif build_image: self.handler.make_temp_dir() recipepath = self.handler.get_temp_recipe_path(rep.base_image) image_name = recipepath.rstrip(".bb") path, sep, image_name = image_name.rpartition("/") image = [] image.append(image_name) rep.writeRecipe(recipepath, self.model) # In the case where we saved the file for the purpose of building # it we should then delete it so that the users workspace doesn't # contain files they haven't explicitly saved there. if not self.save_path: self.files_to_clean.append(recipepath) self.handler.build_targets(image, self.configurator) else: self.handler.build_targets(self.model.get_selected_pn(), self.configurator, "packages") # Disable parts of the menu which shouldn't be used whilst building self.set_menus_sensitive(False) self.nb.set_current_page(1) def set_menus_sensitive(self, sensitive): self.add_layers_action.set_sensitive(sensitive) self.layers_action.set_sensitive(sensitive) self.prefs_action.set_sensitive(sensitive) self.open_action.set_sensitive(sensitive) def back_button_clicked_cb(self, button): self.toggle_createview() def toggle_createview(self): self.set_menus_sensitive(True) self.build.reset() self.nb.set_current_page(0) def build_complete_cb(self, running_build): # Have the handler process BB events again self.handler.building = False self.stopping = False self.back.connect("clicked", self.back_button_clicked_cb) self.back.set_sensitive(True) self.cancel.set_sensitive(False) for f in self.files_to_clean: try: os.remove(f) except OSError: pass self.files_to_clean.remove(f) self.files_to_clean = [] lbl = "<b>Build completed</b>\n\nClick 'Edit Image' to start another build or 'View Messages' to view the messages output during the build." if self.handler.build_type == "image" and self.build_succeeded: deploy = self.handler.get_image_deploy_dir() lbl = lbl + "\n<a href=\"file://%s\" title=\"%s\">Browse folder of built images</a>." % (deploy, deploy) dialog = CrumbsDialog(self, lbl) dialog.add_button("View Messages", gtk.RESPONSE_CANCEL) dialog.add_button("Edit Image", gtk.RESPONSE_OK) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_OK: self.toggle_createview() def build_started_cb(self, running_build): self.back.set_sensitive(False) self.cancel.set_sensitive(True) def include_gplv3_cb(self, toggle): excluded = toggle.get_active() self.handler.toggle_gplv3(excluded) def change_bb_threads(self, spinner): val = spinner.get_value_as_int() self.handler.set_bbthreads(val) def change_make_threads(self, spinner): val = spinner.get_value_as_int() self.handler.set_pmake(val) def toggle_toolchain(self, check): enabled = check.get_active() self.handler.toggle_toolchain(enabled) def toggle_headers(self, check): enabled = check.get_active() self.handler.toggle_toolchain_headers(enabled) def toggle_package_idle_cb(self, opath, image): """ As the operations which we're calling on the model can take a significant amount of time (in the order of seconds) during which the GUI is unresponsive as the main loop is blocked perform them in an idle function which at least enables us to set the busy cursor before the UI is blocked giving the appearance of being responsive. """ # Whether the item is currently included inc = self.model[opath][self.model.COL_INC] # FIXME: due to inpredictability of the removal of packages we are # temporarily disabling this feature # If the item is already included, mark it for removal then # the sweep_up() method finds affected items and marks them # appropriately # if inc: # self.model.mark(opath) # self.model.sweep_up() # # If the item isn't included, mark it for inclusion # else: if not inc: self.model.include_item(item_path=opath, binb="User Selected", image_contents=image) self.set_busy_cursor(False) return False def toggle_package(self, path, model, image=False): inc = model[path][self.model.COL_INC] # Warn user before removing included packages if inc: # FIXME: due to inpredictability of the removal of packages we are # temporarily disabling this feature return # pn = model[path][self.model.COL_NAME] # revdeps = self.model.find_reverse_depends(pn) # if len(revdeps): # lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone and all packages which depend on this will be removed\nPackages which depend on %s include %s." % (pn, pn, ", ".join(revdeps).rstrip(",")) # else: # lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone." % pn # dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) # dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) # dialog.add_button("Remove", gtk.RESPONSE_OK) # response = dialog.run() # dialog.destroy() # if response == gtk.RESPONSE_CANCEL: # return self.set_busy_cursor() # Convert path to path in original model opath = model.convert_path_to_child_path(path) # This is a potentially length call which can block the # main loop, therefore do the work in an idle func to keep # the UI responsive glib.idle_add(self.toggle_package_idle_cb, opath, image) self.dirty = True def toggle_include_cb(self, cell, path, tv): model = tv.get_model() self.toggle_package(path, model) def toggle_pkg_include_cb(self, cell, path, tv): # there's an extra layer of models in the packages case. sort_model = tv.get_model() cpath = sort_model.convert_path_to_child_path(path) self.toggle_package(cpath, sort_model.get_model()) def pkgsaz(self): vbox = gtk.VBox(False, 6) vbox.show() self.pkgsaz_tree = gtk.TreeView() self.pkgsaz_tree.set_headers_visible(True) self.pkgsaz_tree.set_headers_clickable(True) self.pkgsaz_tree.set_enable_search(True) self.pkgsaz_tree.set_search_column(0) self.pkgsaz_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) col = gtk.TreeViewColumn('Package') col.set_clickable(True) col.set_sort_column_id(self.model.COL_NAME) col.set_min_width(220) col1 = gtk.TreeViewColumn('Description') col1.set_resizable(True) col1.set_min_width(360) col2 = gtk.TreeViewColumn('License') col2.set_resizable(True) col2.set_clickable(True) col2.set_sort_column_id(self.model.COL_LIC) col2.set_min_width(170) col3 = gtk.TreeViewColumn('Group') col3.set_clickable(True) col3.set_sort_column_id(self.model.COL_GROUP) col4 = gtk.TreeViewColumn('Included') col4.set_min_width(80) col4.set_max_width(90) col4.set_sort_column_id(self.model.COL_INC) self.pkgsaz_tree.append_column(col) self.pkgsaz_tree.append_column(col1) self.pkgsaz_tree.append_column(col2) self.pkgsaz_tree.append_column(col3) self.pkgsaz_tree.append_column(col4) cell = gtk.CellRendererText() cell1 = gtk.CellRendererText() cell1.set_property('width-chars', 20) cell2 = gtk.CellRendererText() cell2.set_property('width-chars', 20) cell3 = gtk.CellRendererText() cell4 = gtk.CellRendererToggle() cell4.set_property('activatable', True) cell4.connect("toggled", self.toggle_pkg_include_cb, self.pkgsaz_tree) col.pack_start(cell, True) col1.pack_start(cell1, True) col2.pack_start(cell2, True) col3.pack_start(cell3, True) col4.pack_end(cell4, True) col.set_attributes(cell, text=self.model.COL_NAME) col1.set_attributes(cell1, text=self.model.COL_DESC) col2.set_attributes(cell2, text=self.model.COL_LIC) col3.set_attributes(cell3, text=self.model.COL_GROUP) col4.set_attributes(cell4, active=self.model.COL_INC) self.pkgsaz_tree.show() scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) scroll.set_shadow_type(gtk.SHADOW_IN) scroll.add(self.pkgsaz_tree) vbox.pack_start(scroll, True, True, 0) hb = gtk.HBox(False, 0) hb.show() self.search = gtk.Entry() self.search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, "gtk-clear") self.search.connect("icon-release", self.search_entry_clear_cb) self.search.show() self.pkgsaz_tree.set_search_entry(self.search) hb.pack_end(self.search, False, False, 0) label = gtk.Label("Search packages:") label.show() hb.pack_end(label, False, False, 6) vbox.pack_start(hb, False, False, 0) return vbox def search_entry_clear_cb(self, entry, icon_pos, event): entry.set_text("") def tasks(self): vbox = gtk.VBox(False, 6) vbox.show() self.tasks_tree = gtk.TreeView() self.tasks_tree.set_headers_visible(True) self.tasks_tree.set_headers_clickable(False) self.tasks_tree.set_enable_search(True) self.tasks_tree.set_search_column(0) self.tasks_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) col = gtk.TreeViewColumn('Package Collection') col.set_min_width(430) col1 = gtk.TreeViewColumn('Description') col1.set_min_width(430) col2 = gtk.TreeViewColumn('Include') col2.set_min_width(70) col2.set_max_width(80) self.tasks_tree.append_column(col) self.tasks_tree.append_column(col1) self.tasks_tree.append_column(col2) cell = gtk.CellRendererText() cell1 = gtk.CellRendererText() cell2 = gtk.CellRendererToggle() cell2.set_property('activatable', True) cell2.connect("toggled", self.toggle_include_cb, self.tasks_tree) col.pack_start(cell, True) col1.pack_start(cell1, True) col2.pack_end(cell2, True) col.set_attributes(cell, text=self.model.COL_NAME) col1.set_attributes(cell1, text=self.model.COL_DESC) col2.set_attributes(cell2, active=self.model.COL_INC) self.tasks_tree.show() scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) scroll.set_shadow_type(gtk.SHADOW_IN) scroll.add(self.tasks_tree) vbox.pack_start(scroll, True, True, 0) hb = gtk.HBox(False, 0) hb.show() search = gtk.Entry() search.show() self.tasks_tree.set_search_entry(search) hb.pack_end(search, False, False, 0) label = gtk.Label("Search collections:") label.show() hb.pack_end(label, False, False, 6) vbox.pack_start(hb, False, False, 0) return vbox def cancel_build(self, button): if self.stopping: lbl = "<b>Force Stop build?</b>\nYou've already selected Stop once," lbl = lbl + " would you like to 'Force Stop' the build?\n\n" lbl = lbl + "This will stop the build as quickly as possible but may" lbl = lbl + " well leave your build directory in an unusable state" lbl = lbl + " that requires manual steps to fix.\n" dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button("Force Stop", gtk.RESPONSE_YES) else: lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this" lbl = lbl + " build?\n\n'Force Stop' will stop the build as quickly as" lbl = lbl + " possible but may well leave your build directory in an" lbl = lbl + " unusable state that requires manual steps to fix.\n\n" lbl = lbl + "'Stop' will stop the build as soon as all in" lbl = lbl + " progress build tasks are finished. However if a" lbl = lbl + " lengthy compilation phase is in progress this may take" lbl = lbl + " some time." dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button("Stop", gtk.RESPONSE_OK) dialog.add_button("Force Stop", gtk.RESPONSE_YES) response = dialog.run() dialog.destroy() if response != gtk.RESPONSE_CANCEL: self.stopping = True if response == gtk.RESPONSE_OK: self.handler.cancel_build() elif response == gtk.RESPONSE_YES: self.handler.cancel_build(True) def view_build_gui(self): vbox = gtk.VBox(False, 12) vbox.set_border_width(6) vbox.show() build_tv = RunningBuildTreeView(readonly=True) build_tv.show() build_tv.set_model(self.build.model) self.build.model.connect("row-inserted", self.scroll_tv_cb, build_tv) scrolled_view = gtk.ScrolledWindow () scrolled_view.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_view.add(build_tv) scrolled_view.show() vbox.pack_start(scrolled_view, expand=True, fill=True) hbox = gtk.HBox(False, 12) hbox.show() vbox.pack_start(hbox, expand=False, fill=False) self.back = gtk.Button("Back") self.back.show() self.back.set_sensitive(False) hbox.pack_start(self.back, expand=False, fill=False) self.cancel = gtk.Button("Stop Build") self.cancel.connect("clicked", self.cancel_build) self.cancel.show() hbox.pack_end(self.cancel, expand=False, fill=False) return vbox def create_menu(self): menu_items = '''<ui> <menubar name="MenuBar"> <menu action="File"> <menuitem action="Save"/> <menuitem action="Save As"/> <menuitem action="Open"/> <separator/> <menuitem action="AddLayer" label="Add Layer"/> <separator/> <menuitem action="Quit"/> </menu> <menu action="Edit"> <menuitem action="Layers" label="Layers"/> <menuitem action="Preferences"/> </menu> <menu action="Help"> <menuitem action="About"/> </menu> </menubar> </ui>''' uimanager = gtk.UIManager() accel = uimanager.get_accel_group() self.add_accel_group(accel) actions = gtk.ActionGroup('ImageCreator') self.actions = actions actions.add_actions([('Quit', gtk.STOCK_QUIT, None, None, None, self.menu_quit,), ('File', None, '_File'), ('Save', gtk.STOCK_SAVE, None, None, None, self.save_cb), ('Save As', gtk.STOCK_SAVE_AS, None, None, None, self.save_as_cb), ('Edit', None, '_Edit'), ('Help', None, '_Help'), ('About', gtk.STOCK_ABOUT, None, None, None, self.about_cb)]) self.add_layers_action = gtk.Action('AddLayer', 'Add Layer', None, None) self.add_layers_action.connect("activate", self.add_layer_cb) self.actions.add_action(self.add_layers_action) self.layers_action = gtk.Action('Layers', 'Layers', None, None) self.layers_action.connect("activate", self.layers_cb) self.actions.add_action(self.layers_action) self.prefs_action = gtk.Action('Preferences', 'Preferences', None, None) self.prefs_action.connect("activate", self.preferences_cb) self.actions.add_action(self.prefs_action) self.open_action = gtk.Action('Open', 'Open', None, None) self.open_action.connect("activate", self.open_cb) self.actions.add_action(self.open_action) uimanager.insert_action_group(actions, 0) uimanager.add_ui_from_string(menu_items) menubar = uimanager.get_widget('/MenuBar') menubar.show_all() return menubar def info_button_clicked_cb(self, button): info = "We cannot accurately predict the image contents before they are built so instead a best" info = info + " attempt at estimating what the image will contain is listed." dialog = CrumbsDialog(self, info, gtk.STOCK_DIALOG_INFO) dialog.add_buttons(gtk.STOCK_CLOSE, gtk.RESPONSE_OK) resp = dialog.run() dialog.destroy() def create_build_gui(self): vbox = gtk.VBox(False, 12) vbox.set_border_width(6) vbox.show() hbox = gtk.HBox(False, 12) hbox.show() vbox.pack_start(hbox, expand=False, fill=False) label = gtk.Label("Machine:") label.show() hbox.pack_start(label, expand=False, fill=False, padding=6) self.machine_combo = gtk.combo_box_new_text() self.machine_combo.show() self.machine_combo.set_tooltip_text("Selects the architecture of the target board for which you would like to build an image.") hbox.pack_start(self.machine_combo, expand=False, fill=False, padding=6) label = gtk.Label("Base image:") label.show() hbox.pack_start(label, expand=False, fill=False, padding=6) self.image_combo = gtk.ComboBox() self.image_combo.show() self.image_combo.set_tooltip_text("Selects the image on which to base the created image") image_combo_cell = gtk.CellRendererText() self.image_combo.pack_start(image_combo_cell, True) self.image_combo.add_attribute(image_combo_cell, 'text', self.model.COL_NAME) hbox.pack_start(self.image_combo, expand=False, fill=False, padding=6) self.progress = gtk.ProgressBar() self.progress.set_size_request(250, -1) hbox.pack_end(self.progress, expand=False, fill=False, padding=6) ins = gtk.Notebook() vbox.pack_start(ins, expand=True, fill=True) ins.set_show_tabs(True) label = gtk.Label("Packages") label.show() ins.append_page(self.pkgsaz(), tab_label=label) label = gtk.Label("Package Collections") label.show() ins.append_page(self.tasks(), tab_label=label) ins.set_current_page(0) ins.show_all() hbox = gtk.HBox(False, 1) hbox.show() label = gtk.Label("Estimated image contents:") self.model.connect("contents-changed", self.update_package_count_cb, label) label.set_property("xalign", 0.00) label.show() hbox.pack_start(label, expand=False, fill=False, padding=6) info = gtk.Button("?") info.set_tooltip_text("What does this mean?") info.show() info.connect("clicked", self.info_button_clicked_cb) hbox.pack_start(info, expand=False, fill=False, padding=6) vbox.pack_start(hbox, expand=False, fill=False, padding=6) con = self.contents() con.show() vbox.pack_start(con, expand=True, fill=True) bbox = gtk.HButtonBox() bbox.set_spacing(12) bbox.set_layout(gtk.BUTTONBOX_END) bbox.show() vbox.pack_start(bbox, expand=False, fill=False) reset = gtk.Button("Reset") reset.connect("clicked", self.reset_clicked_cb) reset.show() bbox.add(reset) bake = gtk.Button("Bake") bake.connect("clicked", self.bake_clicked_cb) bake.show() bbox.add(bake) return vbox def update_package_count_cb(self, model, count, label): lbl = "Estimated image contents (%s packages):" % count label.set_text(lbl) def contents(self): self.contents_tree = gtk.TreeView() self.contents_tree.set_headers_visible(True) self.contents_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) # allow searching in the package column self.contents_tree.set_search_column(0) self.contents_tree.set_enable_search(True) col = gtk.TreeViewColumn('Package') col.set_sort_column_id(0) col.set_min_width(430) col1 = gtk.TreeViewColumn('Brought in by') col1.set_resizable(True) col1.set_min_width(430) self.contents_tree.append_column(col) self.contents_tree.append_column(col1) cell = gtk.CellRendererText() cell1 = gtk.CellRendererText() cell1.set_property('width-chars', 20) col.pack_start(cell, True) col1.pack_start(cell1, True) col.set_attributes(cell, text=self.model.COL_NAME) col1.set_attributes(cell1, text=self.model.COL_BINB) self.contents_tree.show() scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) scroll.set_shadow_type(gtk.SHADOW_IN) scroll.add(self.contents_tree) return scroll
class MainWindow(gtk.Window): def __init__(self, taskmodel, handler, configurator, prefs, layers, mach): gtk.Window.__init__(self) # global state self.curr_mach = mach self.machine_handler_id = None self.image_combo_id = None self.generating = False self.files_to_clean = [] self.selected_image = None self.selected_packages = None self.stopping = False self.model = taskmodel self.model.connect("tasklist-populated", self.update_model) self.model.connect("image-changed", self.image_changed_string_cb) self.handler = handler self.configurator = configurator self.prefs = prefs self.layers = layers self.save_path = None self.dirty = False self.build_succeeded = False self.connect("delete-event", self.destroy_window) self.set_title("Image Creator") self.set_icon_name("applications-development") self.set_default_size(1000, 650) self.build = RunningBuild(sequential=True) self.build.connect("build-failed", self.running_build_failed_cb) self.build.connect("build-succeeded", self.running_build_succeeded_cb) self.build.connect("build-started", self.build_started_cb) self.build.connect("build-complete", self.build_complete_cb) vbox = gtk.VBox(False, 0) vbox.set_border_width(0) vbox.show() self.add(vbox) self.menu = self.create_menu() vbox.pack_start(self.menu, False) createview = self.create_build_gui() self.back = None self.cancel = None buildview = self.view_build_gui() self.nb = gtk.Notebook() self.nb.append_page(createview) self.nb.append_page(buildview) self.nb.set_current_page(0) self.nb.set_show_tabs(False) vbox.pack_start(self.nb, expand=True, fill=True) def destroy_window(self, widget, event): self.quit() def menu_quit(self, action): self.quit() def quit(self): if self.dirty and len(self.model.contents): question = "Would you like to save your customisations?" dialog = CrumbsDialog(self, question, gtk.STOCK_DIALOG_WARNING) dialog.add_buttons(gtk.STOCK_NO, gtk.RESPONSE_NO, gtk.STOCK_YES, gtk.RESPONSE_YES) resp = dialog.run() dialog.destroy() if resp == gtk.RESPONSE_YES: if not self.save_path: self.get_save_path() if self.save_path: self.save_recipe_file() rep = self.model.get_build_rep() rep.writeRecipe(self.save_path, self.model) # Prevent the busy cursor being shown after hob exits if quit is called # whilst the busy cursor is set self.set_busy_cursor(False) self.handler.remove_temp_dir() gtk.main_quit() """ In the case of a fatal error give the user as much information as possible and then exit. """ def fatal_error_cb(self, handler, errormsg, phase): lbl = "<b>Error!</b>\nThere was an unrecoverable error during the" lbl = lbl + " <i>%s</i> phase of BitBake. This must be" % phase lbl = lbl + " rectified before the GUI will function. The error" lbl = lbl + " message which which caused this is:\n\n\"%s\"" % errormsg dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_ERROR) dialog.add_button("Exit", gtk.RESPONSE_OK) response = dialog.run() dialog.destroy() self.set_busy_cursor(False) gtk.main_quit() def scroll_tv_cb(self, model, path, it, view): view.scroll_to_cell(path) def running_build_succeeded_cb(self, running_build): self.build_succeeded = True def running_build_failed_cb(self, running_build): self.build_succeeded = False def image_changed_string_cb(self, model, new_image): self.selected_image = new_image # disconnect the image combo's signal handler if self.image_combo_id: self.image_combo.disconnect(self.image_combo_id) self.image_combo_id = None cnt = 0 it = self.model.images.get_iter_first() while it: path = self.model.images.get_path(it) if self.model.images[path][self.model.COL_NAME] == new_image: self.image_combo.set_active(cnt) break it = self.model.images.iter_next(it) cnt = cnt + 1 # Reconnect the signal handler if not self.image_combo_id: self.image_combo_id = self.image_combo.connect( "changed", self.image_changed_cb) def image_changed_cb(self, combo): model = self.image_combo.get_model() it = self.image_combo.get_active_iter() if it: path = model.get_path(it) # Firstly, deselect the previous image userp, _ = self.model.get_selected_packages() self.model.reset() # Now select the new image and save its path in case we # change the image later self.toggle_package(path, model, image=True) if len(userp): self.model.set_selected_packages(userp) self.selected_image = model[path][self.model.COL_NAME] def reload_triggered_cb(self, handler, image, packages): if image: self.selected_image = image if len(packages): self.selected_packages = packages.split() def data_generated(self, handler): self.generating = False self.enable_widgets() def machine_combo_changed_cb(self, combo, handler): mach = combo.get_active_text() if mach != self.curr_mach: self.curr_mach = mach # Flush this straight to the file as MACHINE is changed # independently of other 'Preferences' self.configurator.setConfVar('MACHINE', mach) self.configurator.writeConf() handler.set_machine(mach) handler.reload_data() def update_machines(self, handler, machines): active = 0 # disconnect the signal handler before updating the combo model if self.machine_handler_id: self.machine_combo.disconnect(self.machine_handler_id) self.machine_handler_id = None model = self.machine_combo.get_model() if model: model.clear() for machine in machines: self.machine_combo.append_text(machine) if machine == self.curr_mach: self.machine_combo.set_active(active) active = active + 1 self.machine_handler_id = self.machine_combo.connect( "changed", self.machine_combo_changed_cb, handler) def set_busy_cursor(self, busy=True): """ Convenience method to set the cursor to a spinner when executing a potentially lengthy process. A busy value of False will set the cursor back to the default left pointer. """ if busy: cursor = gtk.gdk.Cursor(gtk.gdk.WATCH) else: # TODO: presumably the default cursor is different on RTL # systems. Can we determine the default cursor? Or at least # the cursor which is set before we change it? cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) window = self.get_root_window() window.set_cursor(cursor) def busy_idle_func(self): if self.generating: self.progress.pulse() return True else: if not self.image_combo_id: self.image_combo_id = self.image_combo.connect( "changed", self.image_changed_cb) self.progress.set_text("Loaded") self.progress.set_fraction(0.0) self.set_busy_cursor(False) return False def busy(self, handler): self.generating = True self.progress.set_text("Loading...") self.set_busy_cursor() if self.image_combo_id: self.image_combo.disconnect(self.image_combo_id) self.image_combo_id = None self.progress.pulse() gobject.timeout_add(100, self.busy_idle_func) self.disable_widgets() def enable_widgets(self): self.menu.set_sensitive(True) self.machine_combo.set_sensitive(True) self.image_combo.set_sensitive(True) self.nb.set_sensitive(True) self.contents_tree.set_sensitive(True) def disable_widgets(self): self.menu.set_sensitive(False) self.machine_combo.set_sensitive(False) self.image_combo.set_sensitive(False) self.nb.set_sensitive(False) self.contents_tree.set_sensitive(False) def update_model(self, model): # We want the packages model to be alphabetised and sortable so create # a TreeModelSort to use in the view pkgsaz_model = gtk.TreeModelSort(self.model.packages_model()) pkgsaz_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) # Unset default sort func so that we only toggle between A-Z and # Z-A sorting pkgsaz_model.set_default_sort_func(None) self.pkgsaz_tree.set_model(pkgsaz_model) self.image_combo.set_model(self.model.images_model()) # Without this the image combo is incorrectly sized on first load of the GUI self.image_combo.set_active(0) self.image_combo.set_active(-1) if not self.image_combo_id: self.image_combo_id = self.image_combo.connect( "changed", self.image_changed_cb) # We want the contents to be alphabetised so create a TreeModelSort to # use in the view contents_model = gtk.TreeModelSort(self.model.contents_model()) contents_model.set_sort_column_id(self.model.COL_NAME, gtk.SORT_ASCENDING) # Unset default sort func so that we only toggle between A-Z and # Z-A sorting contents_model.set_default_sort_func(None) self.contents_tree.set_model(contents_model) self.tasks_tree.set_model(self.model.tasks_model()) if self.selected_image: if self.image_combo_id: self.image_combo.disconnect(self.image_combo_id) self.image_combo_id = None self.model.set_selected_image(self.selected_image) if not self.image_combo_id: self.image_combo_id = self.image_combo.connect( "changed", self.image_changed_cb) if self.selected_packages: self.model.set_selected_packages(self.selected_packages) def reset_clicked_cb(self, button): lbl = "<b>Reset your selections?</b>\n\nAny new changes you have made will be lost" dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button("Reset", gtk.RESPONSE_OK) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_OK: self.reset_build() self.search.set_text("") self.selected_image = None return def reset_build(self): if self.image_combo_id: self.image_combo.disconnect(self.image_combo_id) self.image_combo_id = None self.image_combo.set_active(-1) self.model.reset() if not self.image_combo_id: self.image_combo_id = self.image_combo.connect( "changed", self.image_changed_cb) def layers_cb(self, action): resp = self.layers.run() self.layers.save_current_layers() self.layers.hide() def add_layer_cb(self, action): self.layers.find_layer(self) self.layers.save_current_layers() def preferences_cb(self, action): resp = self.prefs.run() self.prefs.write_changes() self.prefs.hide() def about_cb(self, action): about = gtk.AboutDialog() about.set_name("Image Creator") about.set_copyright("Copyright (C) 2011 Intel Corporation") about.set_authors(["Joshua Lock <*****@*****.**>"]) 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 chooser.destroy() self.model.load_image_rep(rep) self.dirty = False def bake_clicked_cb(self, button): build_image = True rep = self.model.get_build_rep() # If no base image and no user selected packages don't build anything if not self.selected_image and not len(rep.userpkgs): lbl = "<b>No selections made</b>\nYou have not made any selections" lbl = lbl + " so there isn't anything to bake at this time." dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) dialog.run() dialog.destroy() return # Else if no base image, ask whether to just build packages or whether # to build a rootfs with the selected packages in elif not self.selected_image: lbl = "<b>Build empty image or only packages?</b>\nA base image" lbl = lbl + " has not been selected.\n\'Empty image' will build" lbl = lbl + " an image with only the selected packages as its" lbl = lbl + " contents.\n'Packages Only' will build only the" lbl = lbl + " selected packages, no image will be created" dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button("Empty Image", gtk.RESPONSE_OK) dialog.add_button("Packages Only", gtk.RESPONSE_YES) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_CANCEL: return elif response == gtk.RESPONSE_YES: build_image = False elif response == gtk.RESPONSE_OK: rep.base_image = "empty" # Ensure at least one value is set in IMAGE_FSTYPES. have_selected_fstype = False if (len(self.prefs.selected_image_types) and len(self.prefs.selected_image_types[0])): have_selected_fstype = True if build_image and not have_selected_fstype: lbl = "<b>No image output type selected</b>\nThere is no image output" lbl = lbl + " selected for the build. Please set an output image type" lbl = lbl + " in the preferences (Edit -> Preferences)." dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_INFO) dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) dialog.run() dialog.destroy() return elif build_image: self.handler.make_temp_dir() recipepath = self.handler.get_temp_recipe_path(rep.base_image) image_name = recipepath.rstrip(".bb") path, sep, image_name = image_name.rpartition("/") image = [] image.append(image_name) rep.writeRecipe(recipepath, self.model) # In the case where we saved the file for the purpose of building # it we should then delete it so that the users workspace doesn't # contain files they haven't explicitly saved there. if not self.save_path: self.files_to_clean.append(recipepath) self.handler.build_targets(image, self.configurator) else: self.handler.build_targets(self.model.get_selected_pn(), self.configurator, "packages") # Disable parts of the menu which shouldn't be used whilst building self.set_menus_sensitive(False) self.nb.set_current_page(1) def set_menus_sensitive(self, sensitive): self.add_layers_action.set_sensitive(sensitive) self.layers_action.set_sensitive(sensitive) self.prefs_action.set_sensitive(sensitive) self.open_action.set_sensitive(sensitive) def back_button_clicked_cb(self, button): self.toggle_createview() def toggle_createview(self): self.set_menus_sensitive(True) self.build.reset() self.nb.set_current_page(0) def build_complete_cb(self, running_build): # Have the handler process BB events again self.handler.building = False self.stopping = False self.back.connect("clicked", self.back_button_clicked_cb) self.back.set_sensitive(True) self.cancel.set_sensitive(False) for f in self.files_to_clean: try: os.remove(f) except OSError: pass self.files_to_clean.remove(f) self.files_to_clean = [] lbl = "<b>Build completed</b>\n\nClick 'Edit Image' to start another build or 'View Messages' to view the messages output during the build." if self.handler.build_type == "image" and self.build_succeeded: deploy = self.handler.get_image_deploy_dir() lbl = lbl + "\n<a href=\"file://%s\" title=\"%s\">Browse folder of built images</a>." % ( deploy, deploy) dialog = CrumbsDialog(self, lbl) dialog.add_button("View Messages", gtk.RESPONSE_CANCEL) dialog.add_button("Edit Image", gtk.RESPONSE_OK) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_OK: self.toggle_createview() def build_started_cb(self, running_build): self.back.set_sensitive(False) self.cancel.set_sensitive(True) def include_gplv3_cb(self, toggle): excluded = toggle.get_active() self.handler.toggle_gplv3(excluded) def change_bb_threads(self, spinner): val = spinner.get_value_as_int() self.handler.set_bbthreads(val) def change_make_threads(self, spinner): val = spinner.get_value_as_int() self.handler.set_pmake(val) def toggle_toolchain(self, check): enabled = check.get_active() self.handler.toggle_toolchain(enabled) def toggle_headers(self, check): enabled = check.get_active() self.handler.toggle_toolchain_headers(enabled) def toggle_package_idle_cb(self, opath, image): """ As the operations which we're calling on the model can take a significant amount of time (in the order of seconds) during which the GUI is unresponsive as the main loop is blocked perform them in an idle function which at least enables us to set the busy cursor before the UI is blocked giving the appearance of being responsive. """ # Whether the item is currently included inc = self.model[opath][self.model.COL_INC] # FIXME: due to inpredictability of the removal of packages we are # temporarily disabling this feature # If the item is already included, mark it for removal then # the sweep_up() method finds affected items and marks them # appropriately # if inc: # self.model.mark(opath) # self.model.sweep_up() # # If the item isn't included, mark it for inclusion # else: if not inc: self.model.include_item(item_path=opath, binb="User Selected", image_contents=image) self.set_busy_cursor(False) return False def toggle_package(self, path, model, image=False): inc = model[path][self.model.COL_INC] # Warn user before removing included packages if inc: # FIXME: due to inpredictability of the removal of packages we are # temporarily disabling this feature return # pn = model[path][self.model.COL_NAME] # revdeps = self.model.find_reverse_depends(pn) # if len(revdeps): # lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone and all packages which depend on this will be removed\nPackages which depend on %s include %s." % (pn, pn, ", ".join(revdeps).rstrip(",")) # else: # lbl = "<b>Remove %s?</b>\n\nThis action cannot be undone." % pn # dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) # dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) # dialog.add_button("Remove", gtk.RESPONSE_OK) # response = dialog.run() # dialog.destroy() # if response == gtk.RESPONSE_CANCEL: # return self.set_busy_cursor() # Convert path to path in original model opath = model.convert_path_to_child_path(path) # This is a potentially length call which can block the # main loop, therefore do the work in an idle func to keep # the UI responsive glib.idle_add(self.toggle_package_idle_cb, opath, image) self.dirty = True def toggle_include_cb(self, cell, path, tv): model = tv.get_model() self.toggle_package(path, model) def toggle_pkg_include_cb(self, cell, path, tv): # there's an extra layer of models in the packages case. sort_model = tv.get_model() cpath = sort_model.convert_path_to_child_path(path) self.toggle_package(cpath, sort_model.get_model()) def pkgsaz(self): vbox = gtk.VBox(False, 6) vbox.show() self.pkgsaz_tree = gtk.TreeView() self.pkgsaz_tree.set_headers_visible(True) self.pkgsaz_tree.set_headers_clickable(True) self.pkgsaz_tree.set_enable_search(True) self.pkgsaz_tree.set_search_column(0) self.pkgsaz_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) col = gtk.TreeViewColumn('Package') col.set_clickable(True) col.set_sort_column_id(self.model.COL_NAME) col.set_min_width(220) col1 = gtk.TreeViewColumn('Description') col1.set_resizable(True) col1.set_min_width(360) col2 = gtk.TreeViewColumn('License') col2.set_resizable(True) col2.set_clickable(True) col2.set_sort_column_id(self.model.COL_LIC) col2.set_min_width(170) col3 = gtk.TreeViewColumn('Group') col3.set_clickable(True) col3.set_sort_column_id(self.model.COL_GROUP) col4 = gtk.TreeViewColumn('Included') col4.set_min_width(80) col4.set_max_width(90) col4.set_sort_column_id(self.model.COL_INC) self.pkgsaz_tree.append_column(col) self.pkgsaz_tree.append_column(col1) self.pkgsaz_tree.append_column(col2) self.pkgsaz_tree.append_column(col3) self.pkgsaz_tree.append_column(col4) cell = gtk.CellRendererText() cell1 = gtk.CellRendererText() cell1.set_property('width-chars', 20) cell2 = gtk.CellRendererText() cell2.set_property('width-chars', 20) cell3 = gtk.CellRendererText() cell4 = gtk.CellRendererToggle() cell4.set_property('activatable', True) cell4.connect("toggled", self.toggle_pkg_include_cb, self.pkgsaz_tree) col.pack_start(cell, True) col1.pack_start(cell1, True) col2.pack_start(cell2, True) col3.pack_start(cell3, True) col4.pack_end(cell4, True) col.set_attributes(cell, text=self.model.COL_NAME) col1.set_attributes(cell1, text=self.model.COL_DESC) col2.set_attributes(cell2, text=self.model.COL_LIC) col3.set_attributes(cell3, text=self.model.COL_GROUP) col4.set_attributes(cell4, active=self.model.COL_INC) self.pkgsaz_tree.show() scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) scroll.set_shadow_type(gtk.SHADOW_IN) scroll.add(self.pkgsaz_tree) vbox.pack_start(scroll, True, True, 0) hb = gtk.HBox(False, 0) hb.show() self.search = gtk.Entry() self.search.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, "gtk-clear") self.search.connect("icon-release", self.search_entry_clear_cb) self.search.show() self.pkgsaz_tree.set_search_entry(self.search) hb.pack_end(self.search, False, False, 0) label = gtk.Label("Search packages:") label.show() hb.pack_end(label, False, False, 6) vbox.pack_start(hb, False, False, 0) return vbox def search_entry_clear_cb(self, entry, icon_pos, event): entry.set_text("") def tasks(self): vbox = gtk.VBox(False, 6) vbox.show() self.tasks_tree = gtk.TreeView() self.tasks_tree.set_headers_visible(True) self.tasks_tree.set_headers_clickable(False) self.tasks_tree.set_enable_search(True) self.tasks_tree.set_search_column(0) self.tasks_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) col = gtk.TreeViewColumn('Package Collection') col.set_min_width(430) col1 = gtk.TreeViewColumn('Description') col1.set_min_width(430) col2 = gtk.TreeViewColumn('Include') col2.set_min_width(70) col2.set_max_width(80) self.tasks_tree.append_column(col) self.tasks_tree.append_column(col1) self.tasks_tree.append_column(col2) cell = gtk.CellRendererText() cell1 = gtk.CellRendererText() cell2 = gtk.CellRendererToggle() cell2.set_property('activatable', True) cell2.connect("toggled", self.toggle_include_cb, self.tasks_tree) col.pack_start(cell, True) col1.pack_start(cell1, True) col2.pack_end(cell2, True) col.set_attributes(cell, text=self.model.COL_NAME) col1.set_attributes(cell1, text=self.model.COL_DESC) col2.set_attributes(cell2, active=self.model.COL_INC) self.tasks_tree.show() scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) scroll.set_shadow_type(gtk.SHADOW_IN) scroll.add(self.tasks_tree) vbox.pack_start(scroll, True, True, 0) hb = gtk.HBox(False, 0) hb.show() search = gtk.Entry() search.show() self.tasks_tree.set_search_entry(search) hb.pack_end(search, False, False, 0) label = gtk.Label("Search collections:") label.show() hb.pack_end(label, False, False, 6) vbox.pack_start(hb, False, False, 0) return vbox def cancel_build(self, button): if self.stopping: lbl = "<b>Force Stop build?</b>\nYou've already selected Stop once," lbl = lbl + " would you like to 'Force Stop' the build?\n\n" lbl = lbl + "This will stop the build as quickly as possible but may" lbl = lbl + " well leave your build directory in an unusable state" lbl = lbl + " that requires manual steps to fix.\n" dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button("Force Stop", gtk.RESPONSE_YES) else: lbl = "<b>Stop build?</b>\n\nAre you sure you want to stop this" lbl = lbl + " build?\n\n'Force Stop' will stop the build as quickly as" lbl = lbl + " possible but may well leave your build directory in an" lbl = lbl + " unusable state that requires manual steps to fix.\n\n" lbl = lbl + "'Stop' will stop the build as soon as all in" lbl = lbl + " progress build tasks are finished. However if a" lbl = lbl + " lengthy compilation phase is in progress this may take" lbl = lbl + " some time." dialog = CrumbsDialog(self, lbl, gtk.STOCK_DIALOG_WARNING) dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) dialog.add_button("Stop", gtk.RESPONSE_OK) dialog.add_button("Force Stop", gtk.RESPONSE_YES) response = dialog.run() dialog.destroy() if response != gtk.RESPONSE_CANCEL: self.stopping = True if response == gtk.RESPONSE_OK: self.handler.cancel_build() elif response == gtk.RESPONSE_YES: self.handler.cancel_build(True) def view_build_gui(self): vbox = gtk.VBox(False, 12) vbox.set_border_width(6) vbox.show() build_tv = RunningBuildTreeView(readonly=True) build_tv.show() build_tv.set_model(self.build.model) self.build.model.connect("row-inserted", self.scroll_tv_cb, build_tv) scrolled_view = gtk.ScrolledWindow() scrolled_view.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_view.add(build_tv) scrolled_view.show() vbox.pack_start(scrolled_view, expand=True, fill=True) hbox = gtk.HBox(False, 12) hbox.show() vbox.pack_start(hbox, expand=False, fill=False) self.back = gtk.Button("Back") self.back.show() self.back.set_sensitive(False) hbox.pack_start(self.back, expand=False, fill=False) self.cancel = gtk.Button("Stop Build") self.cancel.connect("clicked", self.cancel_build) self.cancel.show() hbox.pack_end(self.cancel, expand=False, fill=False) return vbox def create_menu(self): menu_items = '''<ui> <menubar name="MenuBar"> <menu action="File"> <menuitem action="Save"/> <menuitem action="Save As"/> <menuitem action="Open"/> <separator/> <menuitem action="AddLayer" label="Add Layer"/> <separator/> <menuitem action="Quit"/> </menu> <menu action="Edit"> <menuitem action="Layers" label="Layers"/> <menuitem action="Preferences"/> </menu> <menu action="Help"> <menuitem action="About"/> </menu> </menubar> </ui>''' uimanager = gtk.UIManager() accel = uimanager.get_accel_group() self.add_accel_group(accel) actions = gtk.ActionGroup('ImageCreator') self.actions = actions actions.add_actions([ ( 'Quit', gtk.STOCK_QUIT, None, None, None, self.menu_quit, ), ('File', None, '_File'), ('Save', gtk.STOCK_SAVE, None, None, None, self.save_cb), ('Save As', gtk.STOCK_SAVE_AS, None, None, None, self.save_as_cb), ('Edit', None, '_Edit'), ('Help', None, '_Help'), ('About', gtk.STOCK_ABOUT, None, None, None, self.about_cb) ]) self.add_layers_action = gtk.Action('AddLayer', 'Add Layer', None, None) self.add_layers_action.connect("activate", self.add_layer_cb) self.actions.add_action(self.add_layers_action) self.layers_action = gtk.Action('Layers', 'Layers', None, None) self.layers_action.connect("activate", self.layers_cb) self.actions.add_action(self.layers_action) self.prefs_action = gtk.Action('Preferences', 'Preferences', None, None) self.prefs_action.connect("activate", self.preferences_cb) self.actions.add_action(self.prefs_action) self.open_action = gtk.Action('Open', 'Open', None, None) self.open_action.connect("activate", self.open_cb) self.actions.add_action(self.open_action) uimanager.insert_action_group(actions, 0) uimanager.add_ui_from_string(menu_items) menubar = uimanager.get_widget('/MenuBar') menubar.show_all() return menubar def info_button_clicked_cb(self, button): info = "We cannot accurately predict the image contents before they are built so instead a best" info = info + " attempt at estimating what the image will contain is listed." dialog = CrumbsDialog(self, info, gtk.STOCK_DIALOG_INFO) dialog.add_buttons(gtk.STOCK_CLOSE, gtk.RESPONSE_OK) resp = dialog.run() dialog.destroy() def create_build_gui(self): vbox = gtk.VBox(False, 12) vbox.set_border_width(6) vbox.show() hbox = gtk.HBox(False, 12) hbox.show() vbox.pack_start(hbox, expand=False, fill=False) label = gtk.Label("Machine:") label.show() hbox.pack_start(label, expand=False, fill=False, padding=6) self.machine_combo = gtk.combo_box_new_text() self.machine_combo.show() self.machine_combo.set_tooltip_text( "Selects the architecture of the target board for which you would like to build an image." ) hbox.pack_start(self.machine_combo, expand=False, fill=False, padding=6) label = gtk.Label("Base image:") label.show() hbox.pack_start(label, expand=False, fill=False, padding=6) self.image_combo = gtk.ComboBox() self.image_combo.show() self.image_combo.set_tooltip_text( "Selects the image on which to base the created image") image_combo_cell = gtk.CellRendererText() self.image_combo.pack_start(image_combo_cell, True) self.image_combo.add_attribute(image_combo_cell, 'text', self.model.COL_NAME) hbox.pack_start(self.image_combo, expand=False, fill=False, padding=6) self.progress = gtk.ProgressBar() self.progress.set_size_request(250, -1) hbox.pack_end(self.progress, expand=False, fill=False, padding=6) ins = gtk.Notebook() vbox.pack_start(ins, expand=True, fill=True) ins.set_show_tabs(True) label = gtk.Label("Packages") label.show() ins.append_page(self.pkgsaz(), tab_label=label) label = gtk.Label("Package Collections") label.show() ins.append_page(self.tasks(), tab_label=label) ins.set_current_page(0) ins.show_all() hbox = gtk.HBox(False, 1) hbox.show() label = gtk.Label("Estimated image contents:") self.model.connect("contents-changed", self.update_package_count_cb, label) label.set_property("xalign", 0.00) label.show() hbox.pack_start(label, expand=False, fill=False, padding=6) info = gtk.Button("?") info.set_tooltip_text("What does this mean?") info.show() info.connect("clicked", self.info_button_clicked_cb) hbox.pack_start(info, expand=False, fill=False, padding=6) vbox.pack_start(hbox, expand=False, fill=False, padding=6) con = self.contents() con.show() vbox.pack_start(con, expand=True, fill=True) bbox = gtk.HButtonBox() bbox.set_spacing(12) bbox.set_layout(gtk.BUTTONBOX_END) bbox.show() vbox.pack_start(bbox, expand=False, fill=False) reset = gtk.Button("Reset") reset.connect("clicked", self.reset_clicked_cb) reset.show() bbox.add(reset) bake = gtk.Button("Bake") bake.connect("clicked", self.bake_clicked_cb) bake.show() bbox.add(bake) return vbox def update_package_count_cb(self, model, count, label): lbl = "Estimated image contents (%s packages):" % count label.set_text(lbl) def contents(self): self.contents_tree = gtk.TreeView() self.contents_tree.set_headers_visible(True) self.contents_tree.get_selection().set_mode(gtk.SELECTION_SINGLE) # allow searching in the package column self.contents_tree.set_search_column(0) self.contents_tree.set_enable_search(True) col = gtk.TreeViewColumn('Package') col.set_sort_column_id(0) col.set_min_width(430) col1 = gtk.TreeViewColumn('Brought in by') col1.set_resizable(True) col1.set_min_width(430) self.contents_tree.append_column(col) self.contents_tree.append_column(col1) cell = gtk.CellRendererText() cell1 = gtk.CellRendererText() cell1.set_property('width-chars', 20) col.pack_start(cell, True) col1.pack_start(cell1, True) col.set_attributes(cell, text=self.model.COL_NAME) col1.set_attributes(cell1, text=self.model.COL_BINB) self.contents_tree.show() scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) scroll.set_shadow_type(gtk.SHADOW_IN) scroll.add(self.contents_tree) return scroll
class HobHandler(gobject.GObject): """ This object does BitBake event handling for the hob gui. """ __gsignals__ = { "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, )), "parsing-warning": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, )), "sanity-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, )), "recipe-populated": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), "package-populated": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } (GENERATE_CONFIGURATION, GENERATE_RECIPES, GENERATE_PACKAGES, GENERATE_IMAGE, POPULATE_PACKAGEINFO, SANITY_CHECK) = range(6) (SUB_FILES_MACH, SUB_PARSE_CONFIG, SUB_SANITY_CHECK, SUB_GNERATE_TGTS, SUB_GENERATE_PKGINFO, SUB_BUILD_RECIPES, SUB_BUILD_IMAGE) = range(7) def __init__(self, server, recipe_model, package_model): super(HobHandler, self).__init__() self.build = RunningBuild(sequential=True) self.recipe_model = recipe_model self.package_model = package_model self.commands_async = [] self.generating = False self.current_phase = None self.building = False self.recipe_queue = [] self.package_queue = [] self.server = server self.error_msg = "" self.initcmd = None self.parsing = False def set_busy(self): if not self.generating: self.emit("generating-data") self.generating = True def clear_busy(self): if self.generating: self.emit("data-generated") self.generating = False def runCommand(self, commandline): try: result, error = self.server.runCommand(commandline) if error: raise Exception("Error running command '%s': %s" % (commandline, error)) return result except Exception as e: self.commands_async = [] self.clear_busy() self.emit("command-failed", "Hob Exception - %s" % (str(e))) return None 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.SUB_FILES_MACH: self.runCommand(["findConfigFiles", "MACHINE"]) elif next_command == self.SUB_PARSE_CONFIG: self.runCommand(["enableDataTracking"]) self.runCommand(["parseConfigurationFiles", "", ""]) self.runCommand(["disableDataTracking"]) elif next_command == self.SUB_GNERATE_TGTS: self.runCommand( ["generateTargetsTree", "classes/image.bbclass", []]) elif next_command == self.SUB_GENERATE_PKGINFO: self.runCommand(["triggerEvent", "bb.event.RequestPackageInfo()"]) elif next_command == self.SUB_SANITY_CHECK: self.runCommand(["triggerEvent", "bb.event.SanityCheck()"]) elif next_command == self.SUB_BUILD_RECIPES: self.clear_busy() self.building = True self.runCommand( ["buildTargets", self.recipe_queue, self.default_task]) self.recipe_queue = [] elif next_command == self.SUB_BUILD_IMAGE: self.clear_busy() self.building = True target = self.image if target == "hob-image": hobImage = self.runCommand(["matchFile", "hob-image.bb"]) if self.base_image != "Start with an empty image recipe": baseImage = self.runCommand( ["matchFile", self.base_image + ".bb"]) version = self.runCommand([ "generateNewImage", hobImage, baseImage, self.package_queue, True, "" ]) target += version self.recipe_model.set_custom_image_version(version) targets = [target + ":do_rootfs"] targets.append("virtual/kernel:do_deploy") targets.append("virtual/kernel:do_populate_sysroot") targets.append("grub:do_deploy") targets.append("grub-conf:do_deploy") if self.toolchain: targets.append(target + ":do_populate_sdk") self.runCommand(["buildTargets", targets, self.default_task]) def display_error(self): self.clear_busy() self.emit("command-failed", self.error_msg) self.error_msg = "" if self.building: self.building = False def handle_event(self, event): if not event: return if self.building: self.current_phase = "building" self.build.handle_event(event) if isinstance(event, bb.event.PackageInfo): self.package_model.populate(event._pkginfolist) self.emit("package-populated") self.run_next_command() elif isinstance(event, bb.event.SanityCheckPassed): reparse = self.runCommand(["getVariable", "BB_INVALIDCONF" ]) or None if reparse is True: self.set_var_in_file("BB_INVALIDCONF", False, "local.conf") self.runCommand(["parseConfigurationFiles", "", ""]) self.run_next_command() elif isinstance(event, bb.event.SanityCheckFailed): self.emit("sanity-failed", event._msg) elif isinstance(event, logging.LogRecord): if not self.building: if event.levelno >= logging.ERROR: formatter = bb.msg.BBLogFormatter() msg = formatter.format(event) self.error_msg += msg + '\n' elif event.levelno >= logging.WARNING and self.parsing == True: formatter = bb.msg.BBLogFormatter() msg = formatter.format(event) warn_msg = msg + '\n' self.emit("parsing-warning", warn_msg) elif isinstance(event, bb.event.TargetsTreeGenerated): self.current_phase = "data generation" if event._model: self.recipe_model.populate(event._model) self.emit("recipe-populated") elif isinstance(event, bb.event.ConfigFilesFound): self.current_phase = "configuration lookup" var = event._variable values = event._values values.sort() self.emit("config-updated", var, values) elif isinstance(event, bb.event.ConfigFilePathFound): self.current_phase = "configuration lookup" elif isinstance(event, bb.event.FilesMatchingFound): self.current_phase = "configuration lookup" # FIXME: hard coding, should at least be a variable shared between # here and the caller if event._pattern == "rootfs_": formats = [] for match in event._matches: classname, sep, cls = match.rpartition(".") fs, sep, format = classname.rpartition("_") formats.append(format) formats.sort() self.emit("package-formats-updated", formats) elif isinstance(event, bb.command.CommandCompleted): self.current_phase = None self.run_next_command() elif isinstance(event, bb.command.CommandFailed): self.commands_async = [] self.display_error() 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) if isinstance(event, bb.event.ParseStarted): self.parsing = True 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) if isinstance(event, bb.event.ParseCompleted): self.parsing = False if self.error_msg and not self.commands_async: self.display_error() return def init_cooker(self): self.runCommand(["initCooker"]) def reset_cooker(self): self.runCommand(["enableDataTracking"]) self.runCommand(["resetCooker"]) self.runCommand(["disableDataTracking"]) def set_distro(self, distro): self.set_var_in_file("DISTRO", distro, "local.conf") def request_package_info(self): self.commands_async.append(self.SUB_GENERATE_PKGINFO) self.run_next_command(self.POPULATE_PACKAGEINFO) def trigger_sanity_check(self): self.commands_async.append(self.SUB_SANITY_CHECK) self.run_next_command(self.SANITY_CHECK) def generate_configuration(self): self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_FILES_MACH) self.run_next_command(self.GENERATE_CONFIGURATION) def generate_recipes(self): self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_GNERATE_TGTS) self.run_next_command(self.GENERATE_RECIPES) def generate_packages(self, tgts, default_task="build"): targets = [] targets.extend(tgts) self.recipe_queue = targets self.default_task = default_task self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_BUILD_RECIPES) self.run_next_command(self.GENERATE_PACKAGES) def generate_image(self, image, base_image, image_packages=[], toolchain=False, default_task="build"): self.image = image self.base_image = base_image self.toolchain = toolchain self.package_queue = image_packages self.default_task = default_task self.commands_async.append(self.SUB_PARSE_CONFIG) self.commands_async.append(self.SUB_BUILD_IMAGE) self.run_next_command(self.GENERATE_IMAGE) def generate_new_image(self, image, base_image, package_queue, description): base_image = self.runCommand(["matchFile", self.base_image + ".bb"]) self.runCommand([ "generateNewImage", image, base_image, package_queue, False, description ]) def ensure_dir(self, directory): self.runCommand(["ensureDir", directory]) def build_succeeded_async(self): self.building = False def build_failed_async(self): self.initcmd = None self.commands_async = [] self.building = False def cancel_parse(self): self.runCommand(["stateForceShutdown"]) def cancel_build(self, force=False): if force: # Force the cooker to stop as quickly as possible self.runCommand(["stateForceShutdown"]) else: # Wait for tasks to complete before shutting down, this helps # leave the workdir in a usable state self.runCommand(["stateShutdown"]) def reset_build(self): self.build.reset() def _remove_redundant(self, string): ret = [] for i in string.split(): if i not in ret: ret.append(i) return " ".join(ret) def set_var_in_file(self, var, val, default_file=None): self.runCommand(["enableDataTracking"]) self.runCommand(["setVarFile", var, val, default_file, "set"]) self.runCommand(["disableDataTracking"]) def early_assign_var_in_file(self, var, val, default_file=None): self.runCommand(["setVarFile", var, val, default_file, "earlyAssign"]) self.runCommand(["disableDataTracking"]) def remove_var_from_file(self, var): self.runCommand(["removeVarFile", var]) def append_var_in_file(self, var, val, default_file=None): self.runCommand(["setVarFile", var, val, default_file, "append"]) def append_to_bbfiles(self, val): bbfiles = self.runCommand(["getVariable", "BBFILES", "False"]) or "" bbfiles = bbfiles.split() if val not in bbfiles: self.append_var_in_file("BBFILES", val, "local.conf") def get_parameters(self): # retrieve the parameters from bitbake params = {} params["image_list"] = self.runCommand( ["getVariable", "HOB_IMAGELIST"]) or "" params["image_addr"] = self.runCommand( ["getVariable", "DEPLOY_DIR_IMAGE"]) or "" params["image_types"] = self._remove_redundant( self.runCommand(["getVariable", "IMAGE_TYPES"]) or "") params["runnable_image_types"] = self._remove_redundant( self.runCommand(["getVariable", "RUNNABLE_IMAGE_TYPES"]) or "") params["runnable_machine_patterns"] = self._remove_redundant( self.runCommand(["getVariable", "RUNNABLE_MACHINE_PATTERNS"]) or "") params["deployable_image_types"] = self._remove_redundant( self.runCommand(["getVariable", "DEPLOYABLE_IMAGE_TYPES"]) or "") params["kernel_image_type"] = self.runCommand( ["getVariable", "KERNEL_IMAGETYPE"]) or "" params["core_base"] = self.runCommand(["getVariable", "COREBASE" ]) or "" params["staging_dir_native"] = self.runCommand( ["getVariable", "STAGING_DIR_NATIVE"]) or "" params["staging_kernel_dir"] = self.runCommand( ["getVariable", "STAGING_KERNEL_DIR"]) or "" params["tmpdir"] = self.runCommand(["getVariable", "TMPDIR"]) or "" params["distro"] = self.runCommand(["getVariable", "DISTRO"]) or "" return params