hob: re-designed interaction and implementation
Highlights include: * Atempted GNOME HIG compliance * Simplified UI and interaction model * Sorting and type to find in tree views * Preferences dialog to modify local settings * Dialog to add and remove layers * Search in packages list * Save/Load image recipes The build model has been changed, hob will attempt to build all dependent packages of an image and then use the buildFile server method to build the created image. (Bitbake rev: 48e64acaae4a741b9f5630f426fb4e6142755c2c) Signed-off-by: Joshua Lock <josh@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
parent
7fc9c3488f
commit
4cc291c007
|
@ -0,0 +1,278 @@
|
|||
#
|
||||
# BitBake Graphical GTK User Interface
|
||||
#
|
||||
# Copyright (C) 2011 Intel Corporation
|
||||
#
|
||||
# Authored by Joshua Lock <josh@linux.intel.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import gobject
|
||||
import copy
|
||||
import re, os
|
||||
from bb import data
|
||||
|
||||
class Configurator(gobject.GObject):
|
||||
|
||||
"""
|
||||
A GObject to handle writing modified configuration values back
|
||||
to conf files.
|
||||
"""
|
||||
__gsignals__ = {
|
||||
"layers-loaded" : (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE,
|
||||
()),
|
||||
"layers-changed" : (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE,
|
||||
())
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
gobject.GObject.__init__(self)
|
||||
self.local = None
|
||||
self.bblayers = None
|
||||
self.enabled_layers = {}
|
||||
self.loaded_layers = {}
|
||||
self.config = {}
|
||||
self.orig_config = {}
|
||||
|
||||
# NOTE: cribbed from the cooker...
|
||||
def _parse(self, f, data, include=False):
|
||||
try:
|
||||
return bb.parse.handle(f, data, include)
|
||||
except (IOError, bb.parse.ParseError) as exc:
|
||||
parselog.critical("Unable to parse %s: %s" % (f, exc))
|
||||
sys.exit(1)
|
||||
|
||||
def _loadLocalConf(self, path):
|
||||
def getString(var):
|
||||
return bb.data.getVar(var, data, True) or ""
|
||||
|
||||
self.local = path
|
||||
|
||||
if self.orig_config:
|
||||
del self.orig_config
|
||||
self.orig_config = {}
|
||||
|
||||
data = bb.data.init()
|
||||
data = self._parse(self.local, data)
|
||||
|
||||
# We only need to care about certain variables
|
||||
mach = getString('MACHINE')
|
||||
if mach and mach != self.config.get('MACHINE', ''):
|
||||
self.config['MACHINE'] = mach
|
||||
sdkmach = getString('SDKMACHINE')
|
||||
if sdkmach and sdkmach != self.config.get('SDKMACHINE', ''):
|
||||
self.config['SDKMACHINE'] = sdkmach
|
||||
distro = getString('DISTRO')
|
||||
if distro and distro != self.config.get('DISTRO', ''):
|
||||
self.config['DISTRO'] = distro
|
||||
bbnum = getString('BB_NUMBER_THREADS')
|
||||
if bbnum and bbnum != self.config.get('BB_NUMBER_THREADS', ''):
|
||||
self.config['BB_NUMBER_THREADS'] = bbnum
|
||||
pmake = getString('PARALLEL_MAKE')
|
||||
if pmake and pmake != self.config.get('PARALLEL_MAKE', ''):
|
||||
self.config['PARALLEL_MAKE'] = pmake
|
||||
incompat = getString('INCOMPATIBLE_LICENSE')
|
||||
if incompat and incompat != self.config.get('INCOMPATIBLE_LICENSE', ''):
|
||||
self.config['INCOMPATIBLE_LICENSE'] = incompat
|
||||
pclass = getString('PACKAGE_CLASSES')
|
||||
if pclass and pclass != self.config.get('PACKAGE_CLASSES', ''):
|
||||
self.config['PACKAGE_CLASSES'] = pclass
|
||||
|
||||
self.orig_config = copy.deepcopy(self.config)
|
||||
|
||||
def setLocalConfVar(self, var, val):
|
||||
if var in self.config:
|
||||
self.config[var] = val
|
||||
|
||||
def _loadLayerConf(self, path):
|
||||
self.bblayers = path
|
||||
self.enabled_layers = {}
|
||||
self.loaded_layers = {}
|
||||
data = bb.data.init()
|
||||
data = self._parse(self.bblayers, data)
|
||||
layers = (bb.data.getVar('BBLAYERS', data, True) or "").split()
|
||||
for layer in layers:
|
||||
# TODO: we may be better off calling the layer by its
|
||||
# BBFILE_COLLECTIONS value?
|
||||
name = self._getLayerName(layer)
|
||||
self.loaded_layers[name] = layer
|
||||
|
||||
self.enabled_layers = copy.deepcopy(self.loaded_layers)
|
||||
self.emit("layers-loaded")
|
||||
|
||||
def _addConfigFile(self, path):
|
||||
pref, sep, filename = path.rpartition("/")
|
||||
if filename == "local.conf" or filename == "hob.local.conf":
|
||||
self._loadLocalConf(path)
|
||||
elif filename == "bblayers.conf":
|
||||
self._loadLayerConf(path)
|
||||
|
||||
def _splitLayer(self, path):
|
||||
# we only care about the path up to /conf/layer.conf
|
||||
layerpath, conf, end = path.rpartition("/conf/")
|
||||
return layerpath
|
||||
|
||||
def _getLayerName(self, path):
|
||||
# Should this be the collection name?
|
||||
layerpath, sep, name = path.rpartition("/")
|
||||
return name
|
||||
|
||||
def disableLayer(self, layer):
|
||||
if layer in self.enabled_layers:
|
||||
del self.enabled_layers[layer]
|
||||
|
||||
def addLayerConf(self, confpath):
|
||||
layerpath = self._splitLayer(confpath)
|
||||
name = self._getLayerName(layerpath)
|
||||
if name not in self.enabled_layers:
|
||||
self.addLayer(name, layerpath)
|
||||
return name, layerpath
|
||||
|
||||
def addLayer(self, name, path):
|
||||
self.enabled_layers[name] = path
|
||||
|
||||
def _isLayerConfDirty(self):
|
||||
# if a different number of layers enabled to what was
|
||||
# loaded, definitely different
|
||||
if len(self.enabled_layers) != len(self.loaded_layers):
|
||||
return True
|
||||
|
||||
for layer in self.loaded_layers:
|
||||
# if layer loaded but no longer present, definitely dirty
|
||||
if layer not in self.enabled_layers:
|
||||
return True
|
||||
|
||||
for layer in self.enabled_layers:
|
||||
# if this layer wasn't present at load, definitely dirty
|
||||
if layer not in self.loaded_layers:
|
||||
return True
|
||||
# if this layers path has changed, definitely dirty
|
||||
if self.enabled_layers[layer] != self.loaded_layers[layer]:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _constructLayerEntry(self):
|
||||
"""
|
||||
Returns a string representing the new layer selection
|
||||
"""
|
||||
layers = self.enabled_layers.copy()
|
||||
# Construct BBLAYERS entry
|
||||
layer_entry = "BBLAYERS = \" \\\n"
|
||||
if 'meta' in layers:
|
||||
layer_entry = layer_entry + " %s \\\n" % layers['meta']
|
||||
del layers['meta']
|
||||
for layer in layers:
|
||||
layer_entry = layer_entry + " %s \\\n" % layers[layer]
|
||||
layer_entry = layer_entry + " \""
|
||||
|
||||
return "".join(layer_entry)
|
||||
|
||||
def writeLocalConf(self):
|
||||
# Dictionary containing only new or modified variables
|
||||
changed_values = {}
|
||||
for var in self.config:
|
||||
val = self.config[var]
|
||||
if self.orig_config.get(var, None) != val:
|
||||
changed_values[var] = val
|
||||
|
||||
if not len(changed_values):
|
||||
return
|
||||
|
||||
# Create a backup of the local.conf
|
||||
bkup = "%s~" % self.local
|
||||
os.rename(self.local, bkup)
|
||||
|
||||
# read the original conf into a list
|
||||
with open(bkup, 'r') as config:
|
||||
config_lines = config.readlines()
|
||||
|
||||
new_config_lines = ["\n"]
|
||||
for var in changed_values:
|
||||
# Convenience function for re.subn(). If the pattern matches
|
||||
# return a string which contains an assignment using the same
|
||||
# assignment operator as the old assignment.
|
||||
def replace_val(matchobj):
|
||||
var = matchobj.group(1) # config variable
|
||||
op = matchobj.group(2) # assignment operator
|
||||
val = changed_values[var] # new config value
|
||||
return "%s %s \"%s\"" % (var, op, val)
|
||||
|
||||
pattern = '^\s*(%s)\s*([+=?.]+)(.*)' % re.escape(var)
|
||||
p = re.compile(pattern)
|
||||
cnt = 0
|
||||
replaced = False
|
||||
|
||||
# Iterate over the local.conf lines and if they are a match
|
||||
# for the pattern comment out the line and append a new line
|
||||
# with the new VAR op "value" entry
|
||||
for line in config_lines:
|
||||
new_line, replacements = p.subn(replace_val, line)
|
||||
if replacements:
|
||||
config_lines[cnt] = "#%s" % line
|
||||
new_config_lines.append(new_line)
|
||||
replaced = True
|
||||
cnt = cnt + 1
|
||||
|
||||
if not replaced:
|
||||
new_config_lines.append("%s = \"%s\"" % (var, changed_values[var]))
|
||||
|
||||
# Add the modified variables
|
||||
config_lines.extend(new_config_lines)
|
||||
|
||||
# Write the updated lines list object to the local.conf
|
||||
with open(self.local, "w") as n:
|
||||
n.write("".join(config_lines))
|
||||
|
||||
del self.orig_config
|
||||
self.orig_config = copy.deepcopy(self.config)
|
||||
|
||||
def writeLayerConf(self):
|
||||
# If we've not added/removed new layers don't write
|
||||
if not self._isLayerConfDirty():
|
||||
return
|
||||
|
||||
# This pattern should find the existing BBLAYERS
|
||||
pattern = 'BBLAYERS\s=\s\".*\"'
|
||||
|
||||
# Backup the users bblayers.conf
|
||||
bkup = "%s~" % self.bblayers
|
||||
os.rename(self.bblayers, bkup)
|
||||
|
||||
replacement = self._constructLayerEntry()
|
||||
|
||||
with open(bkup, "r") as f:
|
||||
contents = f.read()
|
||||
p = re.compile(pattern, re.DOTALL)
|
||||
new = p.sub(replacement, contents)
|
||||
|
||||
with open(self.bblayers, "w") as n:
|
||||
n.write(new)
|
||||
|
||||
# At some stage we should remove the backup we've created
|
||||
# though we should probably verify it first
|
||||
#os.remove(bkup)
|
||||
|
||||
# set loaded_layers for dirtiness tracking
|
||||
self.loaded_layers = copy.deepcopy(self.enabled_layers)
|
||||
|
||||
self.emit("layers-changed")
|
||||
|
||||
def configFound(self, handler, path):
|
||||
self._addConfigFile(path)
|
||||
|
||||
def loadConfig(self, path):
|
||||
self._addConfigFile(path)
|
|
@ -0,0 +1,61 @@
|
|||
#
|
||||
# BitBake Graphical GTK User Interface
|
||||
#
|
||||
# Copyright (C) 2011 Intel Corporation
|
||||
#
|
||||
# Authored by Joshua Lock <josh@linux.intel.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import gobject
|
||||
import gtk
|
||||
"""
|
||||
The following are convenience classes for implementing GNOME HIG compliant
|
||||
BitBake GUI's
|
||||
In summary: spacing = 12px, border-width = 6px
|
||||
"""
|
||||
|
||||
class CrumbsDialog(gtk.Dialog):
|
||||
"""
|
||||
A GNOME HIG compliant dialog widget.
|
||||
Add buttons with gtk.Dialog.add_button or gtk.Dialog.add_buttons
|
||||
"""
|
||||
def __init__(self, parent=None, label="", icon=gtk.STOCK_INFO):
|
||||
gtk.Dialog.__init__(self, "", parent, gtk.DIALOG_DESTROY_WITH_PARENT)
|
||||
|
||||
#self.set_property("has-separator", False) # note: deprecated in 2.22
|
||||
|
||||
self.set_border_width(6)
|
||||
self.vbox.set_property("spacing", 12)
|
||||
self.action_area.set_property("spacing", 12)
|
||||
self.action_area.set_property("border-width", 6)
|
||||
|
||||
first_row = gtk.HBox(spacing=12)
|
||||
first_row.set_property("border-width", 6)
|
||||
first_row.show()
|
||||
self.vbox.add(first_row)
|
||||
|
||||
self.icon = gtk.Image()
|
||||
self.icon.set_from_stock(icon, gtk.ICON_SIZE_DIALOG)
|
||||
self.icon.set_property("yalign", 0.00)
|
||||
self.icon.show()
|
||||
first_row.add(self.icon)
|
||||
|
||||
self.label = gtk.Label()
|
||||
self.label.set_use_markup(True)
|
||||
self.label.set_line_wrap(True)
|
||||
self.label.set_markup(label)
|
||||
self.label.set_property("yalign", 0.00)
|
||||
self.label.show()
|
||||
first_row.add(self.label)
|
|
@ -19,7 +19,6 @@
|
|||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import gobject
|
||||
from bb.ui.crumbs.progress import ProgressBar
|
||||
|
||||
progress_total = 0
|
||||
|
||||
|
@ -32,43 +31,75 @@ class HobHandler(gobject.GObject):
|
|||
"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,
|
||||
())
|
||||
()),
|
||||
"error" : (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE,
|
||||
(gobject.TYPE_STRING,)),
|
||||
"build-complete" : (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE,
|
||||
()),
|
||||
"reload-triggered" : (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE,
|
||||
(gobject.TYPE_STRING,
|
||||
gobject.TYPE_STRING)),
|
||||
}
|
||||
|
||||
def __init__(self, taskmodel, server):
|
||||
gobject.GObject.__init__(self)
|
||||
self.current_command = None
|
||||
self.building = None
|
||||
self.gplv3_excluded = False
|
||||
self.build_toolchain = False
|
||||
self.build_toolchain_headers = False
|
||||
self.generating = False
|
||||
self.build_queue = []
|
||||
|
||||
self.model = taskmodel
|
||||
self.server = server
|
||||
self.current_command = None
|
||||
self.building = False
|
||||
|
||||
self.command_map = {
|
||||
"findConfigFilesDistro" : ("findConfigFiles", "MACHINE", "findConfigFilesMachine"),
|
||||
"findConfigFilesMachine" : ("generateTargetsTree", "classes/image.bbclass", None),
|
||||
"generateTargetsTree" : (None, None, None),
|
||||
"findConfigFilePathLocal" : ("findConfigFilePath", ["hob.local.conf"], "findConfigFilePathHobLocal"),
|
||||
"findConfigFilePathHobLocal" : ("findConfigFilePath", ["bblayers.conf"], "findConfigFilePathLayers"),
|
||||
"findConfigFilePathLayers" : ("findConfigFiles", ["DISTRO"], "findConfigFilesDistro"),
|
||||
"findConfigFilesDistro" : ("findConfigFiles", ["MACHINE"], "findConfigFilesMachine"),
|
||||
"findConfigFilesMachine" : ("findConfigFiles", ["MACHINE-SDK"], "findConfigFilesSdkMachine"),
|
||||
"findConfigFilesSdkMachine" : ("findFilesMatchingInDir", ["rootfs_", "classes"], "findFilesMatchingPackage"),
|
||||
"findFilesMatchingPackage" : ("generateTargetsTree", ["classes/image.bbclass"], None),
|
||||
"generateTargetsTree" : (None, [], None),
|
||||
}
|
||||
|
||||
def run_next_command(self):
|
||||
# FIXME: this is ugly and I *will* replace it
|
||||
if self.current_command:
|
||||
if not self.generating:
|
||||
self.emit("generating-data")
|
||||
self.generating = True
|
||||
next_cmd = self.command_map[self.current_command]
|
||||
command = next_cmd[0]
|
||||
argument = next_cmd[1]
|
||||
self.current_command = next_cmd[2]
|
||||
if command == "generateTargetsTree":
|
||||
self.emit("generating-data")
|
||||
self.server.runCommand([command, argument])
|
||||
args = [command]
|
||||
args.extend(argument)
|
||||
self.server.runCommand(args)
|
||||
|
||||
def handle_event(self, event, running_build, pbar=None):
|
||||
def handle_event(self, event, running_build, pbar):
|
||||
if not event:
|
||||
return
|
||||
|
||||
|
@ -77,9 +108,9 @@ class HobHandler(gobject.GObject):
|
|||
running_build.handle_event(event)
|
||||
elif isinstance(event, bb.event.TargetsTreeGenerated):
|
||||
self.emit("data-generated")
|
||||
self.generating = False
|
||||
if event._model:
|
||||
self.model.populate(event._model)
|
||||
|
||||
elif isinstance(event, bb.event.ConfigFilesFound):
|
||||
var = event._variable
|
||||
if var == "distro":
|
||||
|
@ -90,28 +121,44 @@ class HobHandler(gobject.GObject):
|
|||
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):
|
||||
path = event._path
|
||||
self.emit("config-found", path)
|
||||
elif isinstance(event, bb.event.FilesMatchingFound):
|
||||
# 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.run_next_command()
|
||||
elif isinstance(event, bb.event.CacheLoadStarted) and pbar:
|
||||
pbar.set_title("Loading cache")
|
||||
elif isinstance(event, bb.command.CommandFailed):
|
||||
self.emit("error", event.error)
|
||||
elif isinstance(event, bb.event.CacheLoadStarted):
|
||||
bb.ui.crumbs.hobeventhandler.progress_total = event.total
|
||||
pbar.update(0, bb.ui.crumbs.hobeventhandler.progress_total)
|
||||
elif isinstance(event, bb.event.CacheLoadProgress) and pbar:
|
||||
pbar.update(event.current, bb.ui.crumbs.hobeventhandler.progress_total)
|
||||
elif isinstance(event, bb.event.CacheLoadCompleted) and pbar:
|
||||
pbar.update(bb.ui.crumbs.hobeventhandler.progress_total, bb.ui.crumbs.hobeventhandler.progress_total)
|
||||
elif isinstance(event, bb.event.ParseStarted) and pbar:
|
||||
pbar.set_text("Loading cache: %s/%s" % (0, bb.ui.crumbs.hobeventhandler.progress_total))
|
||||
elif isinstance(event, bb.event.CacheLoadProgress):
|
||||
pbar.set_text("Loading cache: %s/%s" % (event.current, bb.ui.crumbs.hobeventhandler.progress_total))
|
||||
elif isinstance(event, bb.event.CacheLoadCompleted):
|
||||
pbar.set_text("Loading cache: %s/%s" % (bb.ui.crumbs.hobeventhandler.progress_total, bb.ui.crumbs.hobeventhandler.progress_total))
|
||||
elif isinstance(event, bb.event.ParseStarted):
|
||||
if event.total == 0:
|
||||
return
|
||||
pbar.set_title("Processing recipes")
|
||||
bb.ui.crumbs.hobeventhandler.progress_total = event.total
|
||||
pbar.update(0, bb.ui.crumbs.hobeventhandler.progress_total)
|
||||
elif isinstance(event, bb.event.ParseProgress) and pbar:
|
||||
pbar.update(event.current, bb.ui.crumbs.hobeventhandler.progress_total)
|
||||
elif isinstance(event, bb.event.ParseCompleted) and pbar:
|
||||
pbar.hide()
|
||||
|
||||
pbar.set_text("Processing recipes: %s/%s" % (0, bb.ui.crumbs.hobeventhandler.progress_total))
|
||||
elif isinstance(event, bb.event.ParseProgress):
|
||||
pbar.set_text("Processing recipes: %s/%s" % (event.current, bb.ui.crumbs.hobeventhandler.progress_total))
|
||||
elif isinstance(event, bb.event.ParseCompleted):
|
||||
pbar.set_fraction(1.0)
|
||||
return
|
||||
|
||||
def event_handle_idle_func (self, eventHandler, running_build, pbar):
|
||||
|
@ -124,16 +171,95 @@ class HobHandler(gobject.GObject):
|
|||
|
||||
def set_machine(self, machine):
|
||||
self.server.runCommand(["setVariable", "MACHINE", machine])
|
||||
self.current_command = "findConfigFilesMachine"
|
||||
self.run_next_command()
|
||||
|
||||
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 run_build(self, targets):
|
||||
self.building = True
|
||||
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.server.runCommand(["reparseFiles"])
|
||||
self.current_command = "findConfigFilePathLayers"
|
||||
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 run_build(self, tgts):
|
||||
self.building = "image"
|
||||
targets = []
|
||||
targets.append(tgts)
|
||||
if self.build_toolchain and self.build_toolchain_headers:
|
||||
targets = ["meta-toolchain-sdk"] + targets
|
||||
elif self.build_toolchain:
|
||||
targets = ["meta-toolchain"] + targets
|
||||
self.server.runCommand(["buildTargets", targets, "build"])
|
||||
|
||||
def cancel_build(self):
|
||||
# Note: this may not be the right way to stop an in-progress build
|
||||
def build_packages(self, pkgs):
|
||||
self.building = "packages"
|
||||
if 'meta-toolchain' in self.build_queue:
|
||||
self.build_queue.remove('meta-toolchain')
|
||||
pkgs.extend('meta-toolchain')
|
||||
self.server.runCommand(["buildTargets", pkgs, "build"])
|
||||
|
||||
def build_file(self, image):
|
||||
self.building = "image"
|
||||
self.server.runCommand(["buildFile", image, "build"])
|
||||
|
||||
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 toggle_gplv3(self, excluded):
|
||||
if self.gplv3_excluded != excluded:
|
||||
self.gplv3_excluded = excluded
|
||||
if excluded:
|
||||
self.server.runCommand(["setVariable", "INCOMPATIBLE_LICENSE", "GPLv3"])
|
||||
else:
|
||||
self.server.runCommand(["setVariable", "INCOMPATIBLE_LICENSE", ""])
|
||||
|
||||
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 queue_image_recipe_path(self, path):
|
||||
self.build_queue.append(path)
|
||||
|
||||
def build_complete_cb(self, running_build):
|
||||
if len(self.build_queue) > 0:
|
||||
next = self.build_queue.pop(0)
|
||||
if next.endswith('.bb'):
|
||||
self.build_file(next)
|
||||
self.building = 'image'
|
||||
self.build_file(next)
|
||||
else:
|
||||
self.build_packages(next.split(" "))
|
||||
else:
|
||||
self.building = None
|
||||
self.emit("build-complete")
|
||||
|
||||
def set_image_output_type(self, output_type):
|
||||
self.server.runCommand(["setVariable", "IMAGE_FSTYPES", output_type])
|
||||
|
||||
def get_image_deploy_dir(self):
|
||||
return self.server.runCommand(["getVariable", "DEPLOY_DIR_IMAGE"])
|
||||
|
|
|
@ -0,0 +1,293 @@
|
|||
#
|
||||
# BitBake Graphical GTK User Interface
|
||||
#
|
||||
# Copyright (C) 2011 Intel Corporation
|
||||
#
|
||||
# Authored by Joshua Lock <josh@linux.intel.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import gtk
|
||||
from bb.ui.crumbs.configurator import Configurator
|
||||
|
||||
class HobPrefs(gtk.Dialog):
|
||||
"""
|
||||
"""
|
||||
def empty_combo_text(self, combo_text):
|
||||
model = combo_text.get_model()
|
||||
if model:
|
||||
model.clear()
|
||||
|
||||
def output_type_changed_cb(self, combo, handler):
|
||||
ot = combo.get_active_text()
|
||||
if ot != self.curr_output_type:
|
||||
self.curr_output_type = ot
|
||||
handler.set_image_output_type(ot)
|
||||
|
||||
def sdk_machine_combo_changed_cb(self, combo, handler):
|
||||
sdk_mach = combo.get_active_text()
|
||||
if sdk_mach != self.curr_sdk_mach:
|
||||
self.curr_sdk_mach = sdk_mach
|
||||
self.configurator.setLocalConfVar('SDKMACHINE', sdk_mach)
|
||||
handler.set_sdk_machine(sdk_mach)
|
||||
|
||||
def update_sdk_machines(self, handler, sdk_machines):
|
||||
active = 0
|
||||
# disconnect the signal handler before updating the combo model
|
||||
if self.sdk_machine_handler_id:
|
||||
self.sdk_machine_combo.disconnect(self.sdk_machine_handler_id)
|
||||
self.sdk_machine_handler_id = None
|
||||
|
||||
self.empty_combo_text(self.sdk_machine_combo)
|
||||
for sdk_machine in sdk_machines:
|
||||
self.sdk_machine_combo.append_text(sdk_machine)
|
||||
if sdk_machine == self.curr_sdk_mach:
|
||||
self.sdk_machine_combo.set_active(active)
|
||||
active = active + 1
|
||||
|
||||
self.sdk_machine_handler_id = self.sdk_machine_combo.connect("changed", self.sdk_machine_combo_changed_cb, handler)
|
||||
|
||||
def distro_combo_changed_cb(self, combo, handler):
|
||||
distro = combo.get_active_text()
|
||||
if distro != self.curr_distro:
|
||||
self.curr_distro = distro
|
||||
self.configurator.setLocalConfVar('DISTRO', distro)
|
||||
handler.set_distro(distro)
|
||||
self.reload_required = True
|
||||
|
||||
def update_distros(self, handler, distros):
|
||||
active = 0
|
||||
# disconnect the signal handler before updating combo model
|
||||
if self.distro_handler_id:
|
||||
self.distro_combo.disconnect(self.distro_handler_id)
|
||||
self.distro_handler_id = None
|
||||
|
||||
self.empty_combo_text(self.distro_combo)
|
||||
for distro in distros:
|
||||
self.distro_combo.append_text(distro)
|
||||
if distro == self.curr_distro:
|
||||
self.distro_combo.set_active(active)
|
||||
active = active + 1
|
||||
|
||||
self.distro_handler_id = self.distro_combo.connect("changed", self.distro_combo_changed_cb, handler)
|
||||
|
||||
def package_format_combo_changed_cb(self, combo, handler):
|
||||
package_format = combo.get_active_text()
|
||||
if package_format != self.curr_package_format:
|
||||
self.curr_package_format = package_format
|
||||
self.configurator.setLocalConfVar('PACKAGE_CLASSES', 'package_%s' % package_format)
|
||||
handler.set_package_format(package_format)
|
||||
|
||||
def update_package_formats(self, handler, formats):
|
||||
active = 0
|
||||
# disconnect the signal handler before updating the model
|
||||
if self.package_handler_id:
|
||||
self.package_combo.disconnect(self.package_handler_id)
|
||||
self.package_handler_id = None
|
||||
|
||||
self.empty_combo_text(self.package_combo)
|
||||
for format in formats:
|
||||
self.package_combo.append_text(format)
|
||||
if format == self.curr_package_format:
|
||||
self.package_combo.set_active(active)
|
||||
active = active + 1
|
||||
|
||||
self.package_handler_id = self.package_combo.connect("changed", self.package_format_combo_changed_cb, handler)
|
||||
|
||||
def include_gplv3_cb(self, toggle):
|
||||
excluded = toggle.get_active()
|
||||
self.handler.toggle_gplv3(excluded)
|
||||
if excluded:
|
||||
self.configurator.setLocalConfVar('INCOMPATIBLE_LICENSE', 'GPLv3')
|
||||
else:
|
||||
self.configurator.setLocalConfVar('INCOMPATIBLE_LICENSE', '')
|
||||
self.reload_required = True
|
||||
|
||||
def change_bb_threads_cb(self, spinner):
|
||||
val = spinner.get_value_as_int()
|
||||
self.handler.set_bbthreads(val)
|
||||
self.configurator.setLocalConfVar('BB_NUMBER_THREADS', val)
|
||||
|
||||
def change_make_threads_cb(self, spinner):
|
||||
val = spinner.get_value_as_int()
|
||||
self.handler.set_pmake(val)
|
||||
self.configurator.setLocalConfVar('PARALLEL_MAKE', "-j %s" % val)
|
||||
|
||||
def toggle_toolchain_cb(self, check):
|
||||
enabled = check.get_active()
|
||||
self.handler.toggle_toolchain(enabled)
|
||||
|
||||
def toggle_headers_cb(self, check):
|
||||
enabled = check.get_active()
|
||||
self.handler.toggle_toolchain_headers(enabled)
|
||||
|
||||
def set_parent_window(self, parent):
|
||||
self.set_transient_for(parent)
|
||||
|
||||
def write_changes(self):
|
||||
self.configurator.writeLocalConf()
|
||||
|
||||
def prefs_response_cb(self, dialog, response):
|
||||
if self.reload_required:
|
||||
glib.idle_add(self.handler.reload_data)
|
||||
|
||||
def __init__(self, configurator, handler, curr_sdk_mach, curr_distro, pclass,
|
||||
cpu_cnt, pmake, bbthread, image_types):
|
||||
"""
|
||||
"""
|
||||
gtk.Dialog.__init__(self, "Preferences", None,
|
||||
gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
(gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
|
||||
|
||||
self.set_border_width(6)
|
||||
self.vbox.set_property("spacing", 12)
|
||||
self.action_area.set_property("spacing", 12)
|
||||
self.action_area.set_property("border-width", 6)
|
||||
|
||||
self.handler = handler
|
||||
self.configurator = configurator
|
||||
|
||||
self.curr_sdk_mach = curr_sdk_mach
|
||||
self.curr_distro = curr_distro
|
||||
self.curr_package_format = pclass
|
||||
self.curr_output_type = None
|
||||
self.cpu_cnt = cpu_cnt
|
||||
self.pmake = pmake
|
||||
self.bbthread = bbthread
|
||||
self.reload_required = False
|
||||
self.distro_handler_id = None
|
||||
self.sdk_machine_handler_id = None
|
||||
self.package_handler_id = None
|
||||
|
||||
left = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
|
||||
right = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
|
||||
|
||||
label = gtk.Label()
|
||||
label.set_markup("<b>Policy</b>")
|
||||
label.show()
|
||||
frame = gtk.Frame()
|
||||
frame.set_label_widget(label)
|
||||
frame.set_shadow_type(gtk.SHADOW_NONE)
|
||||
frame.show()
|
||||
self.vbox.pack_start(frame)
|
||||
pbox = gtk.VBox(False, 12)
|
||||
pbox.show()
|
||||
frame.add(pbox)
|
||||
hbox = gtk.HBox(False, 12)
|
||||
hbox.show()
|
||||
pbox.pack_start(hbox, expand=False, fill=False, padding=6)
|
||||
# Distro selector
|
||||
label = gtk.Label("Distribution:")
|
||||
label.show()
|
||||
hbox.pack_start(label, expand=False, fill=False, padding=6)
|
||||
self.distro_combo = gtk.combo_box_new_text()
|
||||
self.distro_combo.set_tooltip_text("Select the Yocto distribution you would like to use")
|
||||
self.distro_combo.show()
|
||||
hbox.pack_start(self.distro_combo, expand=False, fill=False, padding=6)
|
||||
# Exclude GPLv3
|
||||
check = gtk.CheckButton("Exclude GPLv3 packages")
|
||||
check.set_tooltip_text("Check this box to prevent GPLv3 packages from being included in your image")
|
||||
check.show()
|
||||
check.connect("toggled", self.include_gplv3_cb)
|
||||
hbox.pack_start(check, expand=False, fill=False, padding=6)
|
||||
hbox = gtk.HBox(False, 12)
|
||||
hbox.show()
|
||||
pbox.pack_start(hbox, expand=False, fill=False, padding=6)
|
||||
# Package format selector
|
||||
label = gtk.Label("Package format:")
|
||||
label.show()
|
||||
hbox.pack_start(label, expand=False, fill=False, padding=6)
|
||||
self.package_combo = gtk.combo_box_new_text()
|
||||
self.package_combo.set_tooltip_text("Select the package format you would like to use in your image")
|
||||
self.package_combo.show()
|
||||
hbox.pack_start(self.package_combo, expand=False, fill=False, padding=6)
|
||||
# Image output type selector
|
||||
label = gtk.Label("Image output type:")
|
||||
label.show()
|
||||
hbox.pack_start(label, expand=False, fill=False, padding=6)
|
||||
output_combo = gtk.combo_box_new_text()
|
||||
if image_types:
|
||||
for it in image_types.split(" "):
|
||||
output_combo.append_text(it)
|
||||
output_combo.connect("changed", self.output_type_changed_cb, handler)
|
||||
else:
|
||||
output_combo.set_sensitive(False)
|
||||
output_combo.show()
|
||||
hbox.pack_start(output_combo)
|
||||
# BitBake
|
||||
label = gtk.Label()
|
||||
label.set_markup("<b>BitBake</b>")
|
||||
label.show()
|
||||
frame = gtk.Frame()
|
||||
frame.set_label_widget(label)
|
||||
frame.set_shadow_type(gtk.SHADOW_NONE)
|
||||
frame.show()
|
||||
self.vbox.pack_start(frame)
|
||||
pbox = gtk.VBox(False, 12)
|
||||
pbox.show()
|
||||
frame.add(pbox)
|
||||
hbox = gtk.HBox(False, 12)
|
||||
hbox.show()
|
||||
pbox.pack_start(hbox, expand=False, fill=False, padding=6)
|
||||
label = gtk.Label("BitBake threads:")
|
||||
label.show()
|
||||
spin_max = 9 #self.cpu_cnt * 3
|
||||
hbox.pack_start(label, expand=False, fill=False, padding=6)
|
||||
bbadj = gtk.Adjustment(value=self.bbthread, lower=1, upper=spin_max, step_incr=1)
|
||||
bbspinner = gtk.SpinButton(adjustment=bbadj, climb_rate=1, digits=0)
|
||||
bbspinner.show()
|
||||
bbspinner.connect("value-changed", self.change_bb_threads_cb)
|
||||
hbox.pack_start(bbspinner, expand=False, fill=False, padding=6)
|
||||
label = gtk.Label("Make threads:")
|
||||
label.show()
|
||||
hbox.pack_start(label, expand=False, fill=False, padding=6)
|
||||
madj = gtk.Adjustment(value=self.pmake, lower=1, upper=spin_max, step_incr=1)
|
||||
makespinner = gtk.SpinButton(adjustment=madj, climb_rate=1, digits=0)
|
||||
makespinner.connect("value-changed", self.change_make_threads_cb)
|
||||
makespinner.show()
|
||||
hbox.pack_start(makespinner, expand=False, fill=False, padding=6)
|
||||
# Toolchain
|
||||
label = gtk.Label()
|
||||
label.set_markup("<b>External Toolchain</b>")
|
||||
label.show()
|
||||
frame = gtk.Frame()
|
||||
frame.set_label_widget(label)
|
||||
frame.set_shadow_type(gtk.SHADOW_NONE)
|
||||
frame.show()
|
||||
self.vbox.pack_start(frame)
|
||||
pbox = gtk.VBox(False, 12)
|
||||
pbox.show()
|
||||
frame.add(pbox)
|
||||
hbox = gtk.HBox(False, 12)
|
||||
hbox.show()
|
||||
pbox.pack_start(hbox, expand=False, fill=False, padding=6)
|
||||
toolcheck = gtk.CheckButton("Build external development toolchain with image")
|
||||
toolcheck.show()
|
||||
toolcheck.connect("toggled", self.toggle_toolchain_cb)
|
||||
hbox.pack_start(toolcheck, expand=False, fill=False, padding=6)
|
||||
hbox = gtk.HBox(False, 12)
|
||||
hbox.show()
|
||||
pbox.pack_start(hbox, expand=False, fill=False, padding=6)
|
||||
label = gtk.Label("Toolchain host:")
|
||||
label.show()
|
||||
hbox.pack_start(label, expand=False, fill=False, padding=6)
|
||||
self.sdk_machine_combo = gtk.combo_box_new_text()
|
||||
self.sdk_machine_combo.set_tooltip_text("Select the host architecture of the external machine")
|
||||
self.sdk_machine_combo.show()
|
||||
hbox.pack_start(self.sdk_machine_combo, expand=False, fill=False, padding=6)
|
||||
headerscheck = gtk.CheckButton("Include development headers with toolchain")
|
||||
headerscheck.show()
|
||||
headerscheck.connect("toggled", self.toggle_headers_cb)
|
||||
hbox.pack_start(headerscheck, expand=False, fill=False, padding=6)
|
||||
self.connect("response", self.prefs_response_cb)
|
|
@ -0,0 +1,136 @@
|
|||
#
|
||||
# BitBake Graphical GTK User Interface
|
||||
#
|
||||
# Copyright (C) 2011 Intel Corporation
|
||||
#
|
||||
# Authored by Joshua Lock <josh@linux.intel.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import gobject
|
||||
import gtk
|
||||
from bb.ui.crumbs.configurator import Configurator
|
||||
|
||||
class LayerEditor(gtk.Dialog):
|
||||
"""
|
||||
Gtk+ Widget for enabling and disabling layers.
|
||||
Layers are added through using an open dialog to find the layer.conf
|
||||
Disabled layers are deleted from conf/bblayers.conf
|
||||
"""
|
||||
def __init__(self, configurator, parent=None):
|
||||
gtk.Dialog.__init__(self, "Layers", None,
|
||||
gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
(gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
|
||||
|
||||
# We want to show a little more of the treeview in the default,
|
||||
# emptier, case
|
||||
self.set_size_request(-1, 300)
|
||||
self.set_border_width(6)
|
||||
self.vbox.set_property("spacing", 0)
|
||||
self.action_area.set_property("border-width", 6)
|
||||
|
||||
self.configurator = configurator
|
||||
self.newly_added = {}
|
||||
|
||||
# Label to inform users that meta is enabled but that you can't
|
||||
# disable it as it'd be a *bad* idea
|
||||
msg = "As the core of the build system the <i>meta</i> layer must always be included and therefore can't be viewed or edited here."
|
||||
lbl = gtk.Label()
|
||||
lbl.show()
|
||||
lbl.set_use_markup(True)
|
||||
lbl.set_markup(msg)
|
||||
lbl.set_line_wrap(True)
|
||||
lbl.set_justify(gtk.JUSTIFY_FILL)
|
||||
self.vbox.pack_start(lbl, expand=False, fill=False, padding=6)
|
||||
|
||||
# Create a treeview in which to list layers
|
||||
# ListStore of Name, Path, Enabled
|
||||
self.layer_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
|
||||
self.tv = gtk.TreeView(self.layer_store)
|
||||
self.tv.set_headers_visible(True)
|
||||
|
||||
col0 = gtk.TreeViewColumn('Name')
|
||||
self.tv.append_column(col0)
|
||||
col1 = gtk.TreeViewColumn('Path')
|
||||
self.tv.append_column(col1)
|
||||
col2 = gtk.TreeViewColumn('Enabled')
|
||||
self.tv.append_column(col2)
|
||||
|
||||
cell0 = gtk.CellRendererText()
|
||||
col0.pack_start(cell0, True)
|
||||
col0.set_attributes(cell0, text=0)
|
||||
cell1 = gtk.CellRendererText()
|
||||
col1.pack_start(cell1, True)
|
||||
col1.set_attributes(cell1, text=1)
|
||||
cell2 = gtk.CellRendererToggle()
|
||||
cell2.connect("toggled", self._toggle_layer_cb)
|
||||
col2.pack_start(cell2, True)
|
||||
col2.set_attributes(cell2, active=2)
|
||||
|
||||
self.tv.show()
|
||||
self.vbox.pack_start(self.tv, expand=True, fill=True, padding=0)
|
||||
|
||||
tb = gtk.Toolbar()
|
||||
tb.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR)
|
||||
tb.set_style(gtk.TOOLBAR_BOTH)
|
||||
tb.set_tooltips(True)
|
||||
tb.show()
|
||||
icon = gtk.Image()
|
||||
icon.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_SMALL_TOOLBAR)
|
||||
icon.show()
|
||||
tb.insert_item("Add Layer", "Add new layer", None, icon,
|
||||
self._find_layer_cb, None, -1)
|
||||
self.vbox.pack_start(tb, expand=False, fill=False, padding=0)
|
||||
|
||||
def set_parent_window(self, parent):
|
||||
self.set_transient_for(parent)
|
||||
|
||||
def load_current_layers(self, data):
|
||||
for layer, path in self.configurator.enabled_layers.items():
|
||||
if layer != 'meta':
|
||||
self.layer_store.append([layer, path, True])
|
||||
|
||||
def save_current_layers(self):
|
||||
self.configurator.writeLayerConf()
|
||||
|
||||
def _toggle_layer_cb(self, cell, path):
|
||||
name = self.layer_store[path][0]
|
||||
toggle = not self.layer_store[path][2]
|
||||
if toggle:
|
||||
self.configurator.addLayer(name, path)
|
||||
else:
|
||||
self.configurator.disableLayer(name)
|
||||
self.layer_store[path][2] = toggle
|
||||
|
||||
def _find_layer_cb(self, button):
|
||||
self.find_layer(self)
|
||||
|
||||
def find_layer(self, parent):
|
||||
dialog = gtk.FileChooserDialog("Add new layer", parent,
|
||||
gtk.FILE_CHOOSER_ACTION_OPEN,
|
||||
(gtk.STOCK_CANCEL, gtk.RESPONSE_NO,
|
||||
gtk.STOCK_OPEN, gtk.RESPONSE_YES))
|
||||
label = gtk.Label("Select the layer.conf of the layer you wish to add")
|
||||
label.show()
|
||||
dialog.set_extra_widget(label)
|
||||
response = dialog.run()
|
||||
path = dialog.get_filename()
|
||||
dialog.destroy()
|
||||
|
||||
if response == gtk.RESPONSE_YES:
|
||||
# FIXME: verify we've actually got a layer conf?
|
||||
if path.endswith(".conf"):
|
||||
name, layerpath = self.configurator.addLayerConf(path)
|
||||
self.newly_added[name] = layerpath
|
||||
self.layer_store.append([name, layerpath, True])
|
|
@ -47,10 +47,16 @@ class RunningBuildModel (gtk.TreeStore):
|
|||
|
||||
class RunningBuild (gobject.GObject):
|
||||
__gsignals__ = {
|
||||
'build-started' : (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE,
|
||||
()),
|
||||
'build-succeeded' : (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE,
|
||||
()),
|
||||
'build-failed' : (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE,
|
||||
()),
|
||||
'build-complete' : (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE,
|
||||
())
|
||||
}
|
||||
|
@ -201,6 +207,7 @@ class RunningBuild (gobject.GObject):
|
|||
|
||||
elif isinstance(event, bb.event.BuildStarted):
|
||||
|
||||
self.emit("build-started")
|
||||
self.model.prepend(None, (None,
|
||||
None,
|
||||
None,
|
||||
|
@ -218,6 +225,9 @@ class RunningBuild (gobject.GObject):
|
|||
Colors.OK,
|
||||
0))
|
||||
|
||||
# Emit a generic "build-complete" signal for things wishing to
|
||||
# handle when the build is finished
|
||||
self.emit("build-complete")
|
||||
# Emit the appropriate signal depending on the number of failures
|
||||
if (failures >= 1):
|
||||
self.emit ("build-failed")
|
||||
|
|
|
@ -20,6 +20,58 @@
|
|||
|
||||
import gtk
|
||||
import gobject
|
||||
import re
|
||||
|
||||
class BuildRep(gobject.GObject):
|
||||
|
||||
def __init__(self, userpkgs, allpkgs, base_image=None):
|
||||
gobject.GObject.__init__(self)
|
||||
self.base_image = base_image
|
||||
self.allpkgs = allpkgs
|
||||
self.userpkgs = userpkgs
|
||||
|
||||
def loadRecipe(self, pathname):
|
||||
contents = []
|
||||
packages = ""
|
||||
base_image = ""
|
||||
|
||||
with open(pathname, 'r') as f:
|
||||
contents = f.readlines()
|
||||
|
||||
pkg_pattern = "^\s*(IMAGE_INSTALL)\s*([+=.?]+)\s*(\"\S*\")"
|
||||
img_pattern = "^\s*(require)\s+(\S+.bb)"
|
||||
|
||||
for line in contents:
|
||||
matchpkg = re.search(pkg_pattern, line)
|
||||
matchimg = re.search(img_pattern, line)
|
||||
if matchpkg:
|
||||
packages = packages + matchpkg.group(3).strip('"')
|
||||
if matchimg:
|
||||
base_image = os.path.basename(matchimg.group(2)).split(".")[0]
|
||||
|
||||
self.base_image = base_image
|
||||
self.userpkgs = packages
|
||||
|
||||
def writeRecipe(self, writepath, model):
|
||||
# FIXME: Need a better way to determine meta_path...
|
||||
template = """
|
||||
# Recipe generated by the HOB
|
||||
|
||||
require %s.bb
|
||||
|
||||
IMAGE_INSTALL += "%s"
|
||||
"""
|
||||
meta_path = model.find_image_path(self.base_image)
|
||||
|
||||
recipe = template % (meta_path, self.userpkgs)
|
||||
|
||||
if os.path.exists(writepath):
|
||||
os.rename(writepath, "%s~" % writepath)
|
||||
|
||||
with open(writepath, 'w') as r:
|
||||
r.write(recipe)
|
||||
|
||||
return writepath
|
||||
|
||||
class TaskListModel(gtk.ListStore):
|
||||
"""
|
||||
|
@ -28,12 +80,18 @@ class TaskListModel(gtk.ListStore):
|
|||
providing convenience functions to access gtk.TreeModel subclasses which
|
||||
provide filtered views of the data.
|
||||
"""
|
||||
(COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC) = range(8)
|
||||
(COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC, COL_IMG, COL_PATH) = range(10)
|
||||
|
||||
__gsignals__ = {
|
||||
"tasklist-populated" : (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE,
|
||||
())
|
||||
()),
|
||||
"contents-changed" : (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE,
|
||||
(gobject.TYPE_INT,)),
|
||||
"image-changed" : (gobject.SIGNAL_RUN_LAST,
|
||||
gobject.TYPE_NONE,
|
||||
(gobject.TYPE_STRING,)),
|
||||
}
|
||||
|
||||
"""
|
||||
|
@ -43,6 +101,7 @@ class TaskListModel(gtk.ListStore):
|
|||
self.tasks = None
|
||||
self.packages = None
|
||||
self.images = None
|
||||
self.selected_image = None
|
||||
|
||||
gtk.ListStore.__init__ (self,
|
||||
gobject.TYPE_STRING,
|
||||
|
@ -52,7 +111,22 @@ class TaskListModel(gtk.ListStore):
|
|||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_STRING,
|
||||
gobject.TYPE_BOOLEAN)
|
||||
gobject.TYPE_BOOLEAN,
|
||||
gobject.TYPE_BOOLEAN,
|
||||
gobject.TYPE_STRING)
|
||||
|
||||
def contents_changed_cb(self, tree_model, path, it=None):
|
||||
pkg_cnt = self.contents.iter_n_children(None)
|
||||
self.emit("contents-changed", pkg_cnt)
|
||||
|
||||
def contents_model_filter(self, model, it):
|
||||
if not model.get_value(it, self.COL_INC) or model.get_value(it, self.COL_TYPE) == 'image':
|
||||
return False
|
||||
name = model.get_value(it, self.COL_NAME)
|
||||
if name.endswith('-native') or name.endswith('-cross'):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
"""
|
||||
Create, if required, and return a filtered gtk.TreeModel
|
||||
|
@ -62,7 +136,9 @@ class TaskListModel(gtk.ListStore):
|
|||
def contents_model(self):
|
||||
if not self.contents:
|
||||
self.contents = self.filter_new()
|
||||
self.contents.set_visible_column(self.COL_INC)
|
||||
self.contents.set_visible_func(self.contents_model_filter)
|
||||
self.contents.connect("row-inserted", self.contents_changed_cb)
|
||||
self.contents.connect("row-deleted", self.contents_changed_cb)
|
||||
return self.contents
|
||||
|
||||
"""
|
||||
|
@ -107,10 +183,10 @@ class TaskListModel(gtk.ListStore):
|
|||
Helper function to determine whether an item is a package
|
||||
"""
|
||||
def package_model_filter(self, model, it):
|
||||
if model.get_value(it, self.COL_TYPE) == 'package':
|
||||
return True
|
||||
else:
|
||||
if model.get_value(it, self.COL_TYPE) != 'package':
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
"""
|
||||
Create, if required, and return a filtered gtk.TreeModel
|
||||
|
@ -129,16 +205,24 @@ class TaskListModel(gtk.ListStore):
|
|||
to notify any listeners that the model is ready
|
||||
"""
|
||||
def populate(self, event_model):
|
||||
# First clear the model, in case repopulating
|
||||
self.clear()
|
||||
for item in event_model["pn"]:
|
||||
atype = 'package'
|
||||
name = item
|
||||
summary = event_model["pn"][item]["summary"]
|
||||
license = event_model["pn"][item]["license"]
|
||||
lic = event_model["pn"][item]["license"]
|
||||
group = event_model["pn"][item]["section"]
|
||||
|
||||
filename = event_model["pn"][item]["filename"]
|
||||
depends = event_model["depends"].get(item, "")
|
||||
rdepends = event_model["rdepends-pn"].get(item, "")
|
||||
depends = depends + rdepends
|
||||
if rdepends:
|
||||
for rdep in rdepends:
|
||||
if event_model["packages"].get(rdep, ""):
|
||||
pn = event_model["packages"][rdep].get("pn", "")
|
||||
if pn:
|
||||
depends.append(pn)
|
||||
|
||||
self.squish(depends)
|
||||
deps = " ".join(depends)
|
||||
|
||||
|
@ -148,14 +232,51 @@ class TaskListModel(gtk.ListStore):
|
|||
atype = 'image'
|
||||
|
||||
self.set(self.append(), self.COL_NAME, name, self.COL_DESC, summary,
|
||||
self.COL_LIC, license, self.COL_GROUP, group,
|
||||
self.COL_LIC, lic, self.COL_GROUP, group,
|
||||
self.COL_DEPS, deps, self.COL_BINB, "",
|
||||
self.COL_TYPE, atype, self.COL_INC, False)
|
||||
self.COL_TYPE, atype, self.COL_INC, False,
|
||||
self.COL_IMG, False, self.COL_PATH, filename)
|
||||
|
||||
self.emit("tasklist-populated")
|
||||
|
||||
"""
|
||||
squish lst so that it doesn't contain any duplicates
|
||||
Load a BuildRep into the model
|
||||
"""
|
||||
def load_image_rep(self, rep):
|
||||
# Unset everything
|
||||
it = self.get_iter_first()
|
||||
while it:
|
||||
path = self.get_path(it)
|
||||
self[path][self.COL_INC] = False
|
||||
self[path][self.COL_IMG] = False
|
||||
it = self.iter_next(it)
|
||||
|
||||
# Iterate the images and disable them all
|
||||
it = self.images.get_iter_first()
|
||||
while it:
|
||||
path = self.images.convert_path_to_child_path(self.images.get_path(it))
|
||||
name = self[path][self.COL_NAME]
|
||||
if name == rep.base_image:
|
||||
self.include_item(path, image_contents=True)
|
||||
else:
|
||||
self[path][self.COL_INC] = False
|
||||
it = self.images.iter_next(it)
|
||||
|
||||
# Mark all of the additional packages for inclusion
|
||||
packages = rep.packages.split(" ")
|
||||
it = self.get_iter_first()
|
||||
while it:
|
||||
path = self.get_path(it)
|
||||
name = self[path][self.COL_NAME]
|
||||
if name in packages:
|
||||
self.include_item(path)
|
||||
packages.remove(name)
|
||||
it = self.iter_next(it)
|
||||
|
||||
self.emit("image-changed", rep.base_image)
|
||||
|
||||
"""
|
||||
squish lst so that it doesn't contain any duplicate entries
|
||||
"""
|
||||
def squish(self, lst):
|
||||
seen = {}
|
||||
|
@ -173,55 +294,58 @@ class TaskListModel(gtk.ListStore):
|
|||
self[path][self.COL_INC] = False
|
||||
|
||||
"""
|
||||
recursively called to mark the item at opath and any package which
|
||||
depends on it for removal
|
||||
"""
|
||||
def mark(self, path):
|
||||
name = self[path][self.COL_NAME]
|
||||
it = self.get_iter_first()
|
||||
def mark(self, opath):
|
||||
removals = []
|
||||
#print("Removing %s" % name)
|
||||
it = self.get_iter_first()
|
||||
name = self[opath][self.COL_NAME]
|
||||
|
||||
self.remove_item_path(path)
|
||||
self.remove_item_path(opath)
|
||||
|
||||
# Remove all dependent packages, update binb
|
||||
while it:
|
||||
path = self.get_path(it)
|
||||
# FIXME: need to ensure partial name matching doesn't happen, regexp?
|
||||
if self[path][self.COL_INC] and self[path][self.COL_DEPS].count(name):
|
||||
#print("%s depended on %s, marking for removal" % (self[path][self.COL_NAME], name))
|
||||
inc = self[path][self.COL_INC]
|
||||
deps = self[path][self.COL_DEPS]
|
||||
binb = self[path][self.COL_BINB]
|
||||
|
||||
# FIXME: need to ensure partial name matching doesn't happen
|
||||
if inc and deps.count(name):
|
||||
# found a dependency, remove it
|
||||
self.mark(path)
|
||||
if self[path][self.COL_INC] and self[path][self.COL_BINB].count(name):
|
||||
binb = self.find_alt_dependency(self[path][self.COL_NAME])
|
||||
#print("%s was brought in by %s, binb set to %s" % (self[path][self.COL_NAME], name, binb))
|
||||
self[path][self.COL_BINB] = binb
|
||||
if inc and binb.count(name):
|
||||
bib = self.find_alt_dependency(name)
|
||||
self[path][self.COL_BINB] = bib
|
||||
|
||||
it = self.iter_next(it)
|
||||
|
||||
"""
|
||||
Remove items from contents if the have an empty COL_BINB (brought in by)
|
||||
caused by all packages they are a dependency of being removed.
|
||||
If the item isn't a package we leave it included.
|
||||
"""
|
||||
def sweep_up(self):
|
||||
model = self.contents
|
||||
removals = []
|
||||
it = self.get_iter_first()
|
||||
it = self.contents.get_iter_first()
|
||||
|
||||
while it:
|
||||
path = self.get_path(it)
|
||||
binb = self[path][self.COL_BINB]
|
||||
if binb == "" or binb is None:
|
||||
#print("Sweeping up %s" % self[path][self.COL_NAME])
|
||||
if not path in removals:
|
||||
removals.extend(path)
|
||||
it = self.iter_next(it)
|
||||
binb = model.get_value(it, self.COL_BINB)
|
||||
itype = model.get_value(it, self.COL_TYPE)
|
||||
|
||||
if itype == 'package' and not binb:
|
||||
opath = model.convert_path_to_child_path(model.get_path(it))
|
||||
if not opath in removals:
|
||||
removals.extend(opath)
|
||||
|
||||
it = model.iter_next(it)
|
||||
|
||||
while removals:
|
||||
path = removals.pop()
|
||||
self.mark(path)
|
||||
|
||||
"""
|
||||
Remove an item from the contents
|
||||
"""
|
||||
def remove_item(self, path):
|
||||
self.mark(path)
|
||||
self.sweep_up()
|
||||
|
||||
"""
|
||||
Find the name of an item in the image contents which depends on the item
|
||||
at contents_path returns either an item name (str) or None
|
||||
|
@ -238,17 +362,10 @@ class TaskListModel(gtk.ListStore):
|
|||
inc = self[path][self.COL_INC]
|
||||
if itname != name and inc and deps.count(name) > 0:
|
||||
# if this item depends on the item, return this items name
|
||||
#print("%s depends on %s" % (itname, name))
|
||||
return itname
|
||||
it = self.iter_next(it)
|
||||
return ""
|
||||
|
||||
"""
|
||||
Convert a path in self to a path in the filtered contents model
|
||||
"""
|
||||
def contents_path_for_path(self, path):
|
||||
return self.contents.convert_child_path_to_path(path)
|
||||
|
||||
"""
|
||||
Check the self.contents gtk.TreeModel for an item
|
||||
where COL_NAME matches item_name
|
||||
|
@ -266,25 +383,30 @@ class TaskListModel(gtk.ListStore):
|
|||
"""
|
||||
Add this item, and any of its dependencies, to the image contents
|
||||
"""
|
||||
def include_item(self, item_path, binb=""):
|
||||
def include_item(self, item_path, binb="", image_contents=False):
|
||||
name = self[item_path][self.COL_NAME]
|
||||
deps = self[item_path][self.COL_DEPS]
|
||||
cur_inc = self[item_path][self.COL_INC]
|
||||
#print("Adding %s for %s dependency" % (name, binb))
|
||||
if not cur_inc:
|
||||
self[item_path][self.COL_INC] = True
|
||||
self[item_path][self.COL_BINB] = binb
|
||||
# We want to do some magic with things which are brought in by the base
|
||||
# image so tag them as so
|
||||
if image_contents:
|
||||
self[item_path][self.COL_IMG] = True
|
||||
if self[item_path][self.COL_TYPE] == 'image':
|
||||
self.selected_image = name
|
||||
|
||||
if deps:
|
||||
#print("Dependencies of %s are %s" % (name, deps))
|
||||
# add all of the deps and set their binb to this item
|
||||
for dep in deps.split(" "):
|
||||
# FIXME: this skipping virtuals can't be right? Unless we choose only to show target
|
||||
# packages? In which case we should handle this server side...
|
||||
# If the contents model doesn't already contain dep, add it
|
||||
if not dep.startswith("virtual") and not self.contents_includes_name(dep):
|
||||
# We only care to show things which will end up in the
|
||||
# resultant image, so filter cross and native recipes
|
||||
if not self.contents_includes_name(dep) and not dep.endswith("-native") and not dep.endswith("-cross"):
|
||||
path = self.find_path_for_item(dep)
|
||||
if path:
|
||||
self.include_item(path, name)
|
||||
self.include_item(path, name, image_contents)
|
||||
else:
|
||||
pass
|
||||
|
||||
|
@ -317,30 +439,78 @@ class TaskListModel(gtk.ListStore):
|
|||
it = self.contents.get_iter_first()
|
||||
|
||||
"""
|
||||
Returns True if one of the selected tasks is an image, False otherwise
|
||||
Returns two lists. One of user selected packages and the other containing
|
||||
all selected packages
|
||||
"""
|
||||
def targets_contains_image(self):
|
||||
it = self.images.get_iter_first()
|
||||
while it:
|
||||
path = self.images.get_path(it)
|
||||
inc = self.images[path][self.COL_INC]
|
||||
if inc:
|
||||
return True
|
||||
it = self.images.iter_next(it)
|
||||
return False
|
||||
|
||||
"""
|
||||
Return a list of all selected items which are not -native or -cross
|
||||
"""
|
||||
def get_targets(self):
|
||||
tasks = []
|
||||
def get_selected_packages(self):
|
||||
allpkgs = []
|
||||
userpkgs = []
|
||||
|
||||
it = self.contents.get_iter_first()
|
||||
while it:
|
||||
path = self.contents.get_path(it)
|
||||
name = self.contents[path][self.COL_NAME]
|
||||
stype = self.contents[path][self.COL_TYPE]
|
||||
if not name.count('-native') and not name.count('-cross'):
|
||||
tasks.append(name)
|
||||
sel = self.contents.get_value(it, self.COL_BINB) == "User Selected"
|
||||
name = self.contents.get_value(it, self.COL_NAME)
|
||||
allpkgs.append(name)
|
||||
if sel:
|
||||
userpkgs.append(name)
|
||||
it = self.contents.iter_next(it)
|
||||
return tasks
|
||||
return userpkgs, allpkgs
|
||||
|
||||
def get_build_rep(self):
|
||||
userpkgs, allpkgs = self.get_selected_packages()
|
||||
image = self.selected_image
|
||||
|
||||
return BuildRep(" ".join(userpkgs), " ".join(allpkgs), image)
|
||||
|
||||
def find_reverse_depends(self, pn):
|
||||
revdeps = []
|
||||
it = self.contents.get_iter_first()
|
||||
|
||||
while it:
|
||||
if self.contents.get_value(it, self.COL_DEPS).count(pn) != 0:
|
||||
revdeps.append(self.contents.get_value(it, self.COL_NAME))
|
||||
it = self.contents.iter_next(it)
|
||||
|
||||
if pn in revdeps:
|
||||
revdeps.remove(pn)
|
||||
return revdeps
|
||||
|
||||
def set_selected_image(self, img):
|
||||
self.selected_image = img
|
||||
path = self.find_path_for_item(img)
|
||||
self.include_item(item_path=path,
|
||||
binb="User Selected",
|
||||
image_contents=True)
|
||||
|
||||
self.emit("image-changed", self.selected_image)
|
||||
|
||||
def set_selected_packages(self, pkglist):
|
||||
selected = pkglist
|
||||
it = self.get_iter_first()
|
||||
|
||||
while it:
|
||||
name = self.get_value(it, self.COL_NAME)
|
||||
if name in pkglist:
|
||||
pkglist.remove(name)
|
||||
path = self.get_path(it)
|
||||
self.include_item(item_path=path,
|
||||
binb="User Selected")
|
||||
if len(pkglist) == 0:
|
||||
return
|
||||
it = self.iter_next(it)
|
||||
|
||||
def find_image_path(self, image):
|
||||
it = self.images.get_iter_first()
|
||||
|
||||
while it:
|
||||
image_name = self.images.get_value(it, self.COL_NAME)
|
||||
if image_name == image:
|
||||
path = self.images.get_value(it, self.COL_PATH)
|
||||
meta_pattern = "(\S*)/(meta*/)(\S*)"
|
||||
meta_match = re.search(meta_pattern, path)
|
||||
if meta_match:
|
||||
_, lyr, bbrel = path.partition(meta_match.group(2))
|
||||
if bbrel:
|
||||
path = bbrel
|
||||
return path
|
||||
it = self.images.iter_next(it)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue