# # BitBake Graphical GTK User Interface # # Copyright (C) 2011 Intel Corporation # # Authored by Joshua Lock # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import gobject import logging import tempfile import datetime progress_total = 0 class HobHandler(gobject.GObject): """ This object does BitBake event handling for the hob gui. """ __gsignals__ = { "machines-updated" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), "sdk-machines-updated": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), "distros-updated" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), "package-formats-found" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), "config-found" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), "generating-data" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), "data-generated" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), "fatal-error" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_STRING,)), "command-failed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), "reload-triggered" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_STRING,)), } (CFG_PATH_LOCAL, CFG_PATH_PRE, CFG_PATH_POST, CFG_PATH_LAYERS, CFG_FILES_DISTRO, CFG_FILES_MACH, CFG_FILES_SDK, FILES_MATCH_CLASS, GENERATE_TGTS, REPARSE_FILES, BUILD_IMAGE) = range(11) def __init__(self, taskmodel, server): gobject.GObject.__init__(self) self.current_command = None self.building = False self.build_toolchain = False self.build_toolchain_headers = False self.generating = False self.build_queue = [] self.current_phase = None self.bbpath_ok = False self.bbfiles_ok = False self.build_type = "image" self.image_dir = os.path.join(tempfile.gettempdir(), 'hob-images') self.model = taskmodel self.server = server deploy_dir = self.server.runCommand(["getVariable", "DEPLOY_DIR"]) self.image_out_dir = os.path.join(deploy_dir, "images") self.image_output_types = self.server.runCommand(["getVariable", "IMAGE_FSTYPES"]).split(" ") self.bbpath = self.server.runCommand(["getVariable", "BBPATH"]) self.bbfiles = self.server.runCommand(["getVariable", "BBFILES"]) def run_next_command(self): if self.current_command and not self.generating: self.emit("generating-data") self.generating = True if self.current_command == self.CFG_PATH_LOCAL: self.current_command = self.CFG_PATH_PRE self.server.runCommand(["findConfigFilePath", "hob-pre.conf"]) elif self.current_command == self.CFG_PATH_PRE: self.current_command = self.CFG_PATH_POST self.server.runCommand(["findConfigFilePath", "hob-post.conf"]) elif self.current_command == self.CFG_PATH_POST: self.current_command = self.CFG_PATH_LAYERS self.server.runCommand(["findConfigFilePath", "bblayers.conf"]) elif self.current_command == self.CFG_PATH_LAYERS: self.current_command = self.CFG_FILES_DISTRO self.server.runCommand(["findConfigFiles", "DISTRO"]) elif self.current_command == self.CFG_FILES_DISTRO: self.current_command = self.CFG_FILES_MACH self.server.runCommand(["findConfigFiles", "MACHINE"]) elif self.current_command == self.CFG_FILES_MACH: self.current_command = self.CFG_FILES_SDK self.server.runCommand(["findConfigFiles", "MACHINE-SDK"]) elif self.current_command == self.CFG_FILES_SDK: self.current_command = self.FILES_MATCH_CLASS self.server.runCommand(["findFilesMatchingInDir", "rootfs_", "classes"]) elif self.current_command == self.FILES_MATCH_CLASS: self.current_command = self.GENERATE_TGTS self.server.runCommand(["generateTargetsTree", "classes/image.bbclass"]) elif self.current_command == self.GENERATE_TGTS: if self.generating: self.emit("data-generated") self.generating = False self.current_command = None elif self.current_command == self.REPARSE_FILES: if self.build_queue: self.current_command = self.BUILD_IMAGE else: self.current_command = self.CFG_PATH_LAYERS self.server.runCommand(["resetCooker"]) self.server.runCommand(["reparseFiles"]) elif self.current_command == self.BUILD_IMAGE: if self.generating: self.emit("data-generated") self.generating = False self.building = True self.server.runCommand(["buildTargets", self.build_queue, "build"]) self.build_queue = [] self.current_command = None def handle_event(self, event, running_build, pbar): if not event: return # If we're running a build, use the RunningBuild event handler if self.building: self.current_phase = "building" running_build.handle_event(event) elif isinstance(event, bb.event.TargetsTreeGenerated): self.current_phase = "data generation" if event._model: self.model.populate(event._model) elif isinstance(event, bb.event.ConfigFilesFound): self.current_phase = "configuration lookup" var = event._variable if var == "distro": distros = event._values distros.sort() self.emit("distros-updated", distros) elif var == "machine": machines = event._values machines.sort() self.emit("machines-updated", machines) elif var == "machine-sdk": sdk_machines = event._values sdk_machines.sort() self.emit("sdk-machines-updated", sdk_machines) elif isinstance(event, bb.event.ConfigFilePathFound): self.current_phase = "configuration lookup" path = event._path self.emit("config-found", path) elif isinstance(event, bb.event.FilesMatchingFound): self.current_phase = "configuration lookup" # FIXME: hard coding, should at least be a variable shared between # 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-found", formats) elif isinstance(event, bb.command.CommandCompleted): self.current_phase = None self.run_next_command() elif isinstance(event, bb.command.CommandFailed): self.emit("command-failed", event.error) elif isinstance(event, bb.event.CacheLoadStarted): self.current_phase = "cache loading" bb.ui.crumbs.hobeventhandler.progress_total = event.total pbar.set_text("Loading cache: %s/%s" % (0, bb.ui.crumbs.hobeventhandler.progress_total)) elif isinstance(event, bb.event.CacheLoadProgress): self.current_phase = "cache loading" pbar.set_text("Loading cache: %s/%s" % (event.current, bb.ui.crumbs.hobeventhandler.progress_total)) elif isinstance(event, bb.event.CacheLoadCompleted): self.current_phase = None pbar.set_text("Loading...") elif isinstance(event, bb.event.ParseStarted): self.current_phase = "recipe parsing" if event.total == 0: return bb.ui.crumbs.hobeventhandler.progress_total = event.total pbar.set_text("Processing recipes: %s/%s" % (0, bb.ui.crumbs.hobeventhandler.progress_total)) elif isinstance(event, bb.event.ParseProgress): self.current_phase = "recipe parsing" pbar.set_text("Processing recipes: %s/%s" % (event.current, bb.ui.crumbs.hobeventhandler.progress_total)) elif isinstance(event, bb.event.ParseCompleted): self.current_phase = None pbar.set_fraction(1.0) pbar.set_text("Loading...") elif isinstance(event, logging.LogRecord): format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s") if event.levelno >= format.CRITICAL: self.emit("fatal-error", event.getMessage(), self.current_phase) return def event_handle_idle_func (self, eventHandler, running_build, pbar): # Consume as many messages as we can in the time available to us event = eventHandler.getEvent() while event: self.handle_event(event, running_build, pbar) event = eventHandler.getEvent() return True def 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_distro(self, distro): self.server.runCommand(["setVariable", "DISTRO", distro]) def set_package_format(self, format): self.server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_%s" % format]) def reload_data(self, config=None): img = self.model.selected_image selected_packages, _ = self.model.get_selected_packages() self.emit("reload-triggered", img, " ".join(selected_packages)) self.current_command = self.REPARSE_FILES self.run_next_command() def set_bbthreads(self, threads): self.server.runCommand(["setVariable", "BB_NUMBER_THREADS", threads]) def set_pmake(self, threads): pmake = "-j %s" % threads self.server.runCommand(["setVariable", "BB_NUMBER_THREADS", pmake]) def build_targets(self, tgts, configurator, build_type="image"): self.build_type = build_type targets = [] nbbp = None nbbf = None targets.extend(tgts) if self.build_toolchain and self.build_toolchain_headers: targets.append("meta-toolchain-sdk") elif self.build_toolchain: targets.append("meta-toolchain") self.build_queue = targets if not self.bbpath_ok: if self.image_dir in self.bbpath.split(":"): self.bbpath_ok = True else: nbbp = self.image_dir if not self.bbfiles_ok: import re pattern = "%s/\*.bb" % self.image_dir for files in self.bbfiles.split(" "): if re.match(pattern, files): self.bbfiles_ok = True if not self.bbfiles_ok: nbbf = "%s/*.bb" % self.image_dir if nbbp or nbbf: configurator.insertTempBBPath(nbbp, nbbf) self.bbpath_ok = True self.bbfiles_ok = True self.current_command = self.REPARSE_FILES self.run_next_command() 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 set_incompatible_license(self, incompatible): self.server.runCommand(["setVariable", "INCOMPATIBLE_LICENSE", incompatible]) def toggle_toolchain(self, enabled): if self.build_toolchain != enabled: self.build_toolchain = enabled def toggle_toolchain_headers(self, enabled): if self.build_toolchain_headers != enabled: self.build_toolchain_headers = enabled def set_fstypes(self, fstypes): self.server.runCommand(["setVariable", "IMAGE_FSTYPES", fstypes]) def add_image_output_type(self, output_type): if output_type not in self.image_output_types: self.image_output_types.append(output_type) fstypes = " ".join(self.image_output_types).lstrip(" ") self.set_fstypes(fstypes) return self.image_output_types def remove_image_output_type(self, output_type): if output_type in self.image_output_types: ind = self.image_output_types.index(output_type) self.image_output_types.pop(ind) fstypes = " ".join(self.image_output_types).lstrip(" ") self.set_fstypes(fstypes) return self.image_output_types def get_image_deploy_dir(self): return self.image_out_dir def make_temp_dir(self): bb.utils.mkdirhier(self.image_dir) def remove_temp_dir(self): bb.utils.remove(self.image_dir, True) def get_temp_recipe_path(self, name): timestamp = datetime.date.today().isoformat() image_file = "hob-%s-variant-%s.bb" % (name, timestamp) recipepath = os.path.join(self.image_dir, image_file) return recipepath