M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions
@@ -0,0 +1,17 @@
#
# Gtk+ UI pieces for BitBake
#
# Copyright (C) 2006-2007 Richard Purdie
#
# 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.
@@ -0,0 +1,455 @@
#
# BitBake Graphical GTK User Interface
#
# Copyright (C) 2008 Intel Corporation
#
# Authored by Rob Bradford <rob@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
import gobject
import threading
import os
import datetime
import time
class BuildConfiguration:
""" Represents a potential *or* historic *or* concrete build. It
encompasses all the things that we need to tell bitbake to do to make it
build what we want it to build.
It also stored the metadata URL and the set of possible machines (and the
distros / images / uris for these. Apart from the metdata URL these are
not serialised to file (since they may be transient). In some ways this
functionality might be shifted to the loader class."""
def __init__ (self):
self.metadata_url = None
# Tuple of (distros, image, urls)
self.machine_options = {}
self.machine = None
self.distro = None
self.image = None
self.urls = []
self.extra_urls = []
self.extra_pkgs = []
def get_machines_model (self):
model = gtk.ListStore (gobject.TYPE_STRING)
for machine in self.machine_options.keys():
model.append ([machine])
return model
def get_distro_and_images_models (self, machine):
distro_model = gtk.ListStore (gobject.TYPE_STRING)
for distro in self.machine_options[machine][0]:
distro_model.append ([distro])
image_model = gtk.ListStore (gobject.TYPE_STRING)
for image in self.machine_options[machine][1]:
image_model.append ([image])
return (distro_model, image_model)
def get_repos (self):
self.urls = self.machine_options[self.machine][2]
return self.urls
# It might be a lot lot better if we stored these in like, bitbake conf
# file format.
@staticmethod
def load_from_file (filename):
conf = BuildConfiguration()
with open(filename, "r") as f:
for line in f:
data = line.split (";")[1]
if (line.startswith ("metadata-url;")):
conf.metadata_url = data.strip()
continue
if (line.startswith ("url;")):
conf.urls += [data.strip()]
continue
if (line.startswith ("extra-url;")):
conf.extra_urls += [data.strip()]
continue
if (line.startswith ("machine;")):
conf.machine = data.strip()
continue
if (line.startswith ("distribution;")):
conf.distro = data.strip()
continue
if (line.startswith ("image;")):
conf.image = data.strip()
continue
return conf
# Serialise to a file. This is part of the build process and we use this
# to be able to repeat a given build (using the same set of parameters)
# but also so that we can include the details of the image / machine /
# distro in the build manager tree view.
def write_to_file (self, filename):
f = open (filename, "w")
lines = []
if (self.metadata_url):
lines += ["metadata-url;%s\n" % (self.metadata_url)]
for url in self.urls:
lines += ["url;%s\n" % (url)]
for url in self.extra_urls:
lines += ["extra-url;%s\n" % (url)]
if (self.machine):
lines += ["machine;%s\n" % (self.machine)]
if (self.distro):
lines += ["distribution;%s\n" % (self.distro)]
if (self.image):
lines += ["image;%s\n" % (self.image)]
f.writelines (lines)
f.close ()
class BuildResult(gobject.GObject):
""" Represents an historic build. Perhaps not successful. But it includes
things such as the files that are in the directory (the output from the
build) as well as a deserialised BuildConfiguration file that is stored in
".conf" in the directory for the build.
This is GObject so that it can be included in the TreeStore."""
(STATE_COMPLETE, STATE_FAILED, STATE_ONGOING) = \
(0, 1, 2)
def __init__ (self, parent, identifier):
gobject.GObject.__init__ (self)
self.date = None
self.files = []
self.status = None
self.identifier = identifier
self.path = os.path.join (parent, identifier)
# Extract the date, since the directory name is of the
# format build-<year><month><day>-<ordinal> we can easily
# pull it out.
# TODO: Better to stat a file?
(_, date, revision) = identifier.split ("-")
print(date)
year = int (date[0:4])
month = int (date[4:6])
day = int (date[6:8])
self.date = datetime.date (year, month, day)
self.conf = None
# By default builds are STATE_FAILED unless we find a "complete" file
# in which case they are STATE_COMPLETE
self.state = BuildResult.STATE_FAILED
for file in os.listdir (self.path):
if (file.startswith (".conf")):
conffile = os.path.join (self.path, file)
self.conf = BuildConfiguration.load_from_file (conffile)
elif (file.startswith ("complete")):
self.state = BuildResult.STATE_COMPLETE
else:
self.add_file (file)
def add_file (self, file):
# Just add the file for now. Don't care about the type.
self.files += [(file, None)]
class BuildManagerModel (gtk.TreeStore):
""" Model for the BuildManagerTreeView. This derives from gtk.TreeStore
but it abstracts nicely what the columns mean and the setup of the columns
in the model. """
(COL_IDENT, COL_DESC, COL_MACHINE, COL_DISTRO, COL_BUILD_RESULT, COL_DATE, COL_STATE) = \
(0, 1, 2, 3, 4, 5, 6)
def __init__ (self):
gtk.TreeStore.__init__ (self,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_OBJECT,
gobject.TYPE_INT64,
gobject.TYPE_INT)
class BuildManager (gobject.GObject):
""" This class manages the historic builds that have been found in the
"results" directory but is also used for starting a new build."""
__gsignals__ = {
'population-finished' : (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
()),
'populate-error' : (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
())
}
def update_build_result (self, result, iter):
# Convert the date into something we can sort by.
date = long (time.mktime (result.date.timetuple()))
# Add a top level entry for the build
self.model.set (iter,
BuildManagerModel.COL_IDENT, result.identifier,
BuildManagerModel.COL_DESC, result.conf.image,
BuildManagerModel.COL_MACHINE, result.conf.machine,
BuildManagerModel.COL_DISTRO, result.conf.distro,
BuildManagerModel.COL_BUILD_RESULT, result,
BuildManagerModel.COL_DATE, date,
BuildManagerModel.COL_STATE, result.state)
# And then we use the files in the directory as the children for the
# top level iter.
for file in result.files:
self.model.append (iter, (None, file[0], None, None, None, date, -1))
# This function is called as an idle by the BuildManagerPopulaterThread
def add_build_result (self, result):
gtk.gdk.threads_enter()
self.known_builds += [result]
self.update_build_result (result, self.model.append (None))
gtk.gdk.threads_leave()
def notify_build_finished (self):
# This is a bit of a hack. If we have a running build running then we
# will have a row in the model in STATE_ONGOING. Find it and make it
# as if it was a proper historic build (well, it is completed now....)
# We need to use the iters here rather than the Python iterator
# interface to the model since we need to pass it into
# update_build_result
iter = self.model.get_iter_first()
while (iter):
(ident, state) = self.model.get(iter,
BuildManagerModel.COL_IDENT,
BuildManagerModel.COL_STATE)
if state == BuildResult.STATE_ONGOING:
result = BuildResult (self.results_directory, ident)
self.update_build_result (result, iter)
iter = self.model.iter_next(iter)
def notify_build_succeeded (self):
# Write the "complete" file so that when we create the BuildResult
# object we put into the model
complete_file_path = os.path.join (self.cur_build_directory, "complete")
f = file (complete_file_path, "w")
f.close()
self.notify_build_finished()
def notify_build_failed (self):
# Without a "complete" file then this will mark the build as failed:
self.notify_build_finished()
# This function is called as an idle
def emit_population_finished_signal (self):
gtk.gdk.threads_enter()
self.emit ("population-finished")
gtk.gdk.threads_leave()
class BuildManagerPopulaterThread (threading.Thread):
def __init__ (self, manager, directory):
threading.Thread.__init__ (self)
self.manager = manager
self.directory = directory
def run (self):
# For each of the "build-<...>" directories ..
if os.path.exists (self.directory):
for directory in os.listdir (self.directory):
if not directory.startswith ("build-"):
continue
build_result = BuildResult (self.directory, directory)
self.manager.add_build_result (build_result)
gobject.idle_add (BuildManager.emit_population_finished_signal,
self.manager)
def __init__ (self, server, results_directory):
gobject.GObject.__init__ (self)
# The builds that we've found from walking the result directory
self.known_builds = []
# Save out the bitbake server, we need this for issuing commands to
# the cooker:
self.server = server
# The TreeStore that we use
self.model = BuildManagerModel ()
# The results directory is where we create (and look for) the
# build-<xyz>-<n> directories. We need to populate ourselves from
# directory
self.results_directory = results_directory
self.populate_from_directory (self.results_directory)
def populate_from_directory (self, directory):
thread = BuildManager.BuildManagerPopulaterThread (self, directory)
thread.start()
# Come up with the name for the next build ident by combining "build-"
# with the date formatted as yyyymmdd and then an ordinal. We do this by
# an optimistic algorithm incrementing the ordinal if we find that it
# already exists.
def get_next_build_ident (self):
today = datetime.date.today ()
datestr = str (today.year) + str (today.month) + str (today.day)
revision = 0
test_name = "build-%s-%d" % (datestr, revision)
test_path = os.path.join (self.results_directory, test_name)
while (os.path.exists (test_path)):
revision += 1
test_name = "build-%s-%d" % (datestr, revision)
test_path = os.path.join (self.results_directory, test_name)
return test_name
# Take a BuildConfiguration and then try and build it based on the
# parameters of that configuration. S
def do_build (self, conf):
server = self.server
# Work out the build directory. Note we actually create the
# directories here since we need to write the ".conf" file. Otherwise
# we could have relied on bitbake's builder thread to actually make
# the directories as it proceeds with the build.
ident = self.get_next_build_ident ()
build_directory = os.path.join (self.results_directory,
ident)
self.cur_build_directory = build_directory
os.makedirs (build_directory)
conffile = os.path.join (build_directory, ".conf")
conf.write_to_file (conffile)
# Add a row to the model representing this ongoing build. It's kinda a
# fake entry. If this build completes or fails then this gets updated
# with the real stuff like the historic builds
date = long (time.time())
self.model.append (None, (ident, conf.image, conf.machine, conf.distro,
None, date, BuildResult.STATE_ONGOING))
try:
server.runCommand(["setVariable", "BUILD_IMAGES_FROM_FEEDS", 1])
server.runCommand(["setVariable", "MACHINE", conf.machine])
server.runCommand(["setVariable", "DISTRO", conf.distro])
server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_ipk"])
server.runCommand(["setVariable", "BBFILES", \
"""${OEROOT}/meta/packages/*/*.bb ${OEROOT}/meta-moblin/packages/*/*.bb"""])
server.runCommand(["setVariable", "TMPDIR", "${OEROOT}/build/tmp"])
server.runCommand(["setVariable", "IPK_FEED_URIS", \
" ".join(conf.get_repos())])
server.runCommand(["setVariable", "DEPLOY_DIR_IMAGE",
build_directory])
server.runCommand(["buildTargets", [conf.image], "rootfs"])
except Exception as e:
print(e)
class BuildManagerTreeView (gtk.TreeView):
""" The tree view for the build manager. This shows the historic builds
and so forth. """
# We use this function to control what goes in the cell since we store
# the date in the model as seconds since the epoch (for sorting) and so we
# need to make it human readable.
def date_format_custom_cell_data_func (self, col, cell, model, iter):
date = model.get (iter, BuildManagerModel.COL_DATE)[0]
datestr = time.strftime("%A %d %B %Y", time.localtime(date))
cell.set_property ("text", datestr)
# This format function controls what goes in the cell. We use this to map
# the integer state to a string and also to colourise the text
def state_format_custom_cell_data_fun (self, col, cell, model, iter):
state = model.get (iter, BuildManagerModel.COL_STATE)[0]
if (state == BuildResult.STATE_ONGOING):
cell.set_property ("text", "Active")
cell.set_property ("foreground", "#000000")
elif (state == BuildResult.STATE_FAILED):
cell.set_property ("text", "Failed")
cell.set_property ("foreground", "#ff0000")
elif (state == BuildResult.STATE_COMPLETE):
cell.set_property ("text", "Complete")
cell.set_property ("foreground", "#00ff00")
else:
cell.set_property ("text", "")
def __init__ (self):
gtk.TreeView.__init__(self)
# Misc descriptiony thing
renderer = gtk.CellRendererText ()
col = gtk.TreeViewColumn (None, renderer,
text=BuildManagerModel.COL_DESC)
self.append_column (col)
# Machine
renderer = gtk.CellRendererText ()
col = gtk.TreeViewColumn ("Machine", renderer,
text=BuildManagerModel.COL_MACHINE)
self.append_column (col)
# distro
renderer = gtk.CellRendererText ()
col = gtk.TreeViewColumn ("Distribution", renderer,
text=BuildManagerModel.COL_DISTRO)
self.append_column (col)
# date (using a custom function for formatting the cell contents it
# takes epoch -> human readable string)
renderer = gtk.CellRendererText ()
col = gtk.TreeViewColumn ("Date", renderer,
text=BuildManagerModel.COL_DATE)
self.append_column (col)
col.set_cell_data_func (renderer,
self.date_format_custom_cell_data_func)
# For status.
renderer = gtk.CellRendererText ()
col = gtk.TreeViewColumn ("Status", renderer,
text = BuildManagerModel.COL_STATE)
self.append_column (col)
col.set_cell_data_func (renderer,
self.state_format_custom_cell_data_fun)
@@ -0,0 +1,346 @@
#
# 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.bblayers = None
self.enabled_layers = {}
self.loaded_layers = {}
self.config = {}
self.orig_config = {}
self.preconf = None
self.postconf = None
# NOTE: cribbed from the cooker...
def _parse(self, f, data, include=False):
try:
return bb.parse.handle(f, data, include)
except (IOError, bb.parse.ParseError) as exc:
parselog.critical("Unable to parse %s: %s" % (f, exc))
sys.exit(1)
def _loadConf(self, path):
def getString(var):
return data.getVar(var, True) or ""
if self.orig_config:
del self.orig_config
self.orig_config = {}
data = bb.data.init()
data = self._parse(path, data)
# We only need to care about certain variables
mach = getString('MACHINE')
if mach and mach != self.config.get('MACHINE', ''):
self.config['MACHINE'] = mach
sdkmach = getString('SDKMACHINE')
if sdkmach and sdkmach != self.config.get('SDKMACHINE', ''):
self.config['SDKMACHINE'] = sdkmach
distro = getString('DISTRO')
if not distro:
distro = "defaultsetup"
if distro and distro != self.config.get('DISTRO', ''):
self.config['DISTRO'] = distro
bbnum = getString('BB_NUMBER_THREADS')
if bbnum and bbnum != self.config.get('BB_NUMBER_THREADS', ''):
self.config['BB_NUMBER_THREADS'] = bbnum
pmake = getString('PARALLEL_MAKE')
if pmake and pmake != self.config.get('PARALLEL_MAKE', ''):
self.config['PARALLEL_MAKE'] = pmake
pclass = getString('PACKAGE_CLASSES')
if pclass and pclass != self.config.get('PACKAGE_CLASSES', ''):
self.config['PACKAGE_CLASSES'] = pclass
fstypes = getString('IMAGE_FSTYPES')
if fstypes and fstypes != self.config.get('IMAGE_FSTYPES', ''):
self.config['IMAGE_FSTYPES'] = fstypes
# Values which aren't always set in the conf must be explicitly
# loaded as empty values for save to work
incompat = getString('INCOMPATIBLE_LICENSE')
if incompat and incompat != self.config.get('INCOMPATIBLE_LICENSE', ''):
self.config['INCOMPATIBLE_LICENSE'] = incompat
else:
self.config['INCOMPATIBLE_LICENSE'] = ""
# Non-standard, namespaces, variables for GUI preferences
toolchain = getString('HOB_BUILD_TOOLCHAIN')
if toolchain and toolchain != self.config.get('HOB_BUILD_TOOLCHAIN', ''):
self.config['HOB_BUILD_TOOLCHAIN'] = toolchain
header = getString('HOB_BUILD_TOOLCHAIN_HEADERS')
if header and header != self.config.get('HOB_BUILD_TOOLCHAIN_HEADERS', ''):
self.config['HOB_BUILD_TOOLCHAIN_HEADERS'] = header
self.orig_config = copy.deepcopy(self.config)
def setConfVar(self, var, val):
self.config[var] = val
def getConfVar(self, var):
if var in self.config:
return self.config[var]
else:
return ""
def _loadLayerConf(self, path):
self.bblayers = path
self.enabled_layers = {}
self.loaded_layers = {}
data = bb.data.init()
data = self._parse(self.bblayers, data)
layers = (data.getVar('BBLAYERS', True) or "").split()
for layer in layers:
# TODO: we may be better off calling the layer by its
# BBFILE_COLLECTIONS value?
name = self._getLayerName(layer)
self.loaded_layers[name] = layer
self.enabled_layers = copy.deepcopy(self.loaded_layers)
self.emit("layers-loaded")
def _addConfigFile(self, path):
conffiles = ["local.conf", "hob-pre.conf", "hob-post.conf"]
pref, sep, filename = path.rpartition("/")
if filename == "hob-pre.conf":
self.preconf = path
if filename == "hob-post.conf":
self.postconf = path
if filename in conffiles:
self._loadConf(path)
elif filename == "bblayers.conf":
self._loadLayerConf(path)
def _splitLayer(self, path):
# we only care about the path up to /conf/layer.conf
layerpath, conf, end = path.rpartition("/conf/")
return layerpath
def _getLayerName(self, path):
# Should this be the collection name?
layerpath, sep, name = path.rpartition("/")
return name
def disableLayer(self, layer):
if layer in self.enabled_layers:
del self.enabled_layers[layer]
def addLayerConf(self, confpath):
layerpath = self._splitLayer(confpath)
name = self._getLayerName(layerpath)
if not layerpath or not name:
return None, None
elif name not in self.enabled_layers:
self.addLayer(name, layerpath)
return name, layerpath
else:
return name, None
def addLayer(self, name, path):
self.enabled_layers[name] = path
def _isLayerConfDirty(self):
# if a different number of layers enabled to what was
# loaded, definitely different
if len(self.enabled_layers) != len(self.loaded_layers):
return True
for layer in self.loaded_layers:
# if layer loaded but no longer present, definitely dirty
if layer not in self.enabled_layers:
return True
for layer in self.enabled_layers:
# if this layer wasn't present at load, definitely dirty
if layer not in self.loaded_layers:
return True
# if this layers path has changed, definitely dirty
if self.enabled_layers[layer] != self.loaded_layers[layer]:
return True
return False
def _constructLayerEntry(self):
"""
Returns a string representing the new layer selection
"""
layers = self.enabled_layers.copy()
# Construct BBLAYERS entry
layer_entry = "BBLAYERS = \" \\\n"
if 'meta' in layers:
layer_entry = layer_entry + " %s \\\n" % layers['meta']
del layers['meta']
for layer in layers:
layer_entry = layer_entry + " %s \\\n" % layers[layer]
layer_entry = layer_entry + " \""
return "".join(layer_entry)
def writeConfFile(self, conffile, contents):
"""
Make a backup copy of conffile and write a new file in its stead with
the lines in the contents list.
"""
# Create a backup of the conf file
bkup = "%s~" % conffile
os.rename(conffile, bkup)
# Write the contents list object to the conf file
with open(conffile, "w") as new:
new.write("".join(contents))
def updateConf(self, orig_lines, changed_values):
new_config_lines = []
for var in changed_values:
# Convenience function for re.subn(). If the pattern matches
# return a string which contains an assignment using the same
# assignment operator as the old assignment.
def replace_val(matchobj):
var = matchobj.group(1) # config variable
op = matchobj.group(2) # assignment operator
val = changed_values[var] # new config value
return "%s %s \"%s\"" % (var, op, val)
pattern = '^\s*(%s)\s*([+=?.]+)(.*)' % re.escape(var)
p = re.compile(pattern)
cnt = 0
replaced = False
# Iterate over the local.conf lines and if they are a match
# for the pattern comment out the line and append a new line
# with the new VAR op "value" entry
for line in orig_lines:
new_line, replacements = p.subn(replace_val, line)
if replacements:
orig_lines[cnt] = "#%s" % line
new_config_lines.append(new_line)
replaced = True
cnt = cnt + 1
if not replaced:
new_config_lines.append("%s = \"%s\"\n" % (var, changed_values[var]))
# Add the modified variables
orig_lines.extend(new_config_lines)
return orig_lines
def writeConf(self):
pre_vars = ["MACHINE", "SDKMACHINE", "DISTRO",
"INCOMPATIBLE_LICENSE"]
post_vars = ["BB_NUMBER_THREADS", "PARALLEL_MAKE", "PACKAGE_CLASSES",
"IMAGE_FSTYPES", "HOB_BUILD_TOOLCHAIN",
"HOB_BUILD_TOOLCHAIN_HEADERS"]
pre_values = {}
post_values = {}
changed_values = {}
pre_lines = None
post_lines = None
for var in self.config:
val = self.config[var]
if self.orig_config.get(var, None) != val:
changed_values[var] = val
if not len(changed_values):
return
for var in changed_values:
if var in pre_vars:
pre_values[var] = changed_values[var]
elif var in post_vars:
post_values[var] = changed_values[var]
with open(self.preconf, 'r') as pre:
pre_lines = pre.readlines()
pre_lines = self.updateConf(pre_lines, pre_values)
if len(pre_lines):
self.writeConfFile(self.preconf, pre_lines)
with open(self.postconf, 'r') as post:
post_lines = post.readlines()
post_lines = self.updateConf(post_lines, post_values)
if len(post_lines):
self.writeConfFile(self.postconf, post_lines)
del self.orig_config
self.orig_config = copy.deepcopy(self.config)
def insertTempBBPath(self, bbpath, bbfiles):
# read the original conf into a list
with open(self.postconf, 'r') as config:
config_lines = config.readlines()
if bbpath:
config_lines.append("BBPATH := \"${BBPATH}:%s\"\n" % bbpath)
if bbfiles:
config_lines.append("BBFILES := \"${BBFILES} %s\"\n" % bbfiles)
self.writeConfFile(self.postconf, config_lines)
def writeLayerConf(self):
# If we've not added/removed new layers don't write
if not self._isLayerConfDirty():
return
# This pattern should find the existing BBLAYERS
pattern = 'BBLAYERS\s=\s\".*\"'
replacement = self._constructLayerEntry()
with open(self.bblayers, "r") as f:
contents = f.read()
p = re.compile(pattern, re.DOTALL)
new = p.sub(replacement, contents)
self.writeConfFile(self.bblayers, new)
# set loaded_layers for dirtiness tracking
self.loaded_layers = copy.deepcopy(self.enabled_layers)
self.emit("layers-changed")
def configFound(self, handler, path):
self._addConfigFile(path)
def loadConfig(self, path):
self._addConfigFile(path)
+61
View File
@@ -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)
@@ -0,0 +1,343 @@
#
# 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 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
@@ -0,0 +1,335 @@
#
# 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
import glib
from bb.ui.crumbs.configurator import Configurator
class HobPrefs(gtk.Dialog):
"""
"""
def empty_combo_text(self, combo_text):
model = combo_text.get_model()
if model:
model.clear()
def output_type_toggled_cb(self, check, handler):
ot = check.get_label()
enabled = check.get_active()
if enabled:
self.selected_image_types = handler.add_image_output_type(ot)
else:
self.selected_image_types = handler.remove_image_output_type(ot)
self.configurator.setConfVar('IMAGE_FSTYPES', "%s" % " ".join(self.selected_image_types).lstrip(" "))
self.reload_required = True
def sdk_machine_combo_changed_cb(self, combo, handler):
sdk_mach = combo.get_active_text()
if sdk_mach != self.curr_sdk_mach:
self.curr_sdk_mach = sdk_mach
self.configurator.setConfVar('SDKMACHINE', sdk_mach)
handler.set_sdk_machine(sdk_mach)
def update_sdk_machines(self, handler, sdk_machines):
active = 0
# disconnect the signal handler before updating the combo model
if self.sdk_machine_handler_id:
self.sdk_machine_combo.disconnect(self.sdk_machine_handler_id)
self.sdk_machine_handler_id = None
self.empty_combo_text(self.sdk_machine_combo)
for sdk_machine in sdk_machines:
self.sdk_machine_combo.append_text(sdk_machine)
if sdk_machine == self.curr_sdk_mach:
self.sdk_machine_combo.set_active(active)
active = active + 1
self.sdk_machine_handler_id = self.sdk_machine_combo.connect("changed", self.sdk_machine_combo_changed_cb, handler)
def distro_combo_changed_cb(self, combo, handler):
distro = combo.get_active_text()
if distro != self.curr_distro:
self.curr_distro = distro
self.configurator.setConfVar('DISTRO', distro)
handler.set_distro(distro)
self.reload_required = True
def update_distros(self, handler, distros):
active = 0
# disconnect the signal handler before updating combo model
if self.distro_handler_id:
self.distro_combo.disconnect(self.distro_handler_id)
self.distro_handler_id = None
self.empty_combo_text(self.distro_combo)
for distro in distros:
self.distro_combo.append_text(distro)
if distro == self.curr_distro:
self.distro_combo.set_active(active)
active = active + 1
self.distro_handler_id = self.distro_combo.connect("changed", self.distro_combo_changed_cb, handler)
def package_format_combo_changed_cb(self, combo, handler):
package_format = combo.get_active_text()
if package_format != self.curr_package_format:
self.curr_package_format = package_format
self.configurator.setConfVar('PACKAGE_CLASSES', 'package_%s' % package_format)
handler.set_package_format(package_format)
self.reload_required = True
def update_package_formats(self, handler, formats):
active = 0
# disconnect the signal handler before updating the model
if self.package_handler_id:
self.package_combo.disconnect(self.package_handler_id)
self.package_handler_id = None
self.empty_combo_text(self.package_combo)
for format in formats:
self.package_combo.append_text(format)
if format == self.curr_package_format:
self.package_combo.set_active(active)
active = active + 1
self.package_handler_id = self.package_combo.connect("changed", self.package_format_combo_changed_cb, handler)
def include_gplv3_cb(self, toggle):
excluded = toggle.get_active()
orig_incompatible = self.configurator.getConfVar('INCOMPATIBLE_LICENSE')
new_incompatible = ""
if excluded:
if not orig_incompatible:
new_incompatible = "GPLv3"
elif not orig_incompatible.find('GPLv3'):
new_incompatible = "%s GPLv3" % orig_incompatible
else:
new_incompatible = orig_incompatible.replace('GPLv3', '')
if new_incompatible != orig_incompatible:
self.handler.set_incompatible_license(new_incompatible)
self.configurator.setConfVar('INCOMPATIBLE_LICENSE', new_incompatible)
self.reload_required = True
def change_bb_threads_cb(self, spinner):
val = spinner.get_value_as_int()
self.handler.set_bbthreads(val)
self.configurator.setConfVar('BB_NUMBER_THREADS', val)
def change_make_threads_cb(self, spinner):
val = spinner.get_value_as_int()
self.handler.set_pmake(val)
self.configurator.setConfVar('PARALLEL_MAKE', "-j %s" % val)
def toggle_toolchain_cb(self, check):
enabled = check.get_active()
toolchain = '0'
if enabled:
toolchain = '1'
self.handler.toggle_toolchain(enabled)
self.configurator.setConfVar('HOB_BUILD_TOOLCHAIN', toolchain)
def toggle_headers_cb(self, check):
enabled = check.get_active()
headers = '0'
if enabled:
headers = '1'
self.handler.toggle_toolchain_headers(enabled)
self.configurator.setConfVar('HOB_BUILD_TOOLCHAIN_HEADERS', headers)
def set_parent_window(self, parent):
self.set_transient_for(parent)
def write_changes(self):
self.configurator.writeConf()
def prefs_response_cb(self, dialog, response):
if self.reload_required:
glib.idle_add(self.handler.reload_data)
self.reload_required = False
def __init__(self, configurator, handler, curr_sdk_mach, curr_distro, pclass,
pmake, bbthread, selected_image_types, all_image_types,
gplv3disabled, build_toolchain, build_toolchain_headers):
"""
"""
gtk.Dialog.__init__(self, "Preferences", None,
gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
self.set_border_width(6)
self.vbox.set_property("spacing", 12)
self.action_area.set_property("spacing", 12)
self.action_area.set_property("border-width", 6)
self.handler = handler
self.configurator = configurator
self.curr_sdk_mach = curr_sdk_mach
self.curr_distro = curr_distro
self.curr_package_format = pclass
self.pmake = pmake
self.bbthread = bbthread
self.selected_image_types = selected_image_types.split(" ")
self.gplv3disabled = gplv3disabled
self.build_toolchain = build_toolchain
self.build_toolchain_headers = build_toolchain_headers
self.reload_required = False
self.distro_handler_id = None
self.sdk_machine_handler_id = None
self.package_handler_id = None
left = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
right = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
label = gtk.Label()
label.set_markup("<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.set_active(self.gplv3disabled)
check.connect("toggled", self.include_gplv3_cb)
hbox.pack_start(check, expand=False, fill=False, padding=6)
hbox = gtk.HBox(False, 12)
hbox.show()
pbox.pack_start(hbox, expand=False, fill=False, padding=6)
# Package format selector
label = gtk.Label("Package format:")
label.show()
hbox.pack_start(label, expand=False, fill=False, padding=6)
self.package_combo = gtk.combo_box_new_text()
self.package_combo.set_tooltip_text("""The package format is that used in creation
of the root filesystem and also dictates the package manager used in your image""")
self.package_combo.show()
hbox.pack_start(self.package_combo, expand=False, fill=False, padding=6)
if all_image_types:
# Image output type selector
label = gtk.Label("Image output types:")
label.show()
hbox.pack_start(label, expand=False, fill=False, padding=6)
chk_cnt = 3
for it in all_image_types.split(" "):
chk_cnt = chk_cnt + 1
if chk_cnt % 6 == 0:
hbox = gtk.HBox(False, 12)
hbox.show()
pbox.pack_start(hbox, expand=False, fill=False, padding=6)
chk = gtk.CheckButton(it)
if it in self.selected_image_types:
chk.set_active(True)
chk.set_tooltip_text("Build an %s image" % it)
chk.connect("toggled", self.output_type_toggled_cb, handler)
chk.show()
hbox.pack_start(chk, expand=False, fill=False, padding=3)
# BitBake
label = gtk.Label()
label.set_markup("<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()
# NOTE: may be a good idea in future to intelligently cap the maximum
# values but we need more data to make an educated decision, for now
# set a high maximum as a value for upper bounds is required by the
# gtk.Adjustment
spin_max = 30 # seems like a high enough arbitrary number
hbox.pack_start(label, expand=False, fill=False, padding=6)
bbadj = gtk.Adjustment(value=self.bbthread, lower=1, upper=spin_max, step_incr=1)
bbspinner = gtk.SpinButton(adjustment=bbadj, climb_rate=1, digits=0)
bbspinner.show()
bbspinner.connect("value-changed", self.change_bb_threads_cb)
hbox.pack_start(bbspinner, expand=False, fill=False, padding=6)
label = gtk.Label("Make threads:")
label.show()
hbox.pack_start(label, expand=False, fill=False, padding=6)
madj = gtk.Adjustment(value=self.pmake, lower=1, upper=spin_max, step_incr=1)
makespinner = gtk.SpinButton(adjustment=madj, climb_rate=1, digits=0)
makespinner.connect("value-changed", self.change_make_threads_cb)
makespinner.show()
hbox.pack_start(makespinner, expand=False, fill=False, padding=6)
# Toolchain
label = gtk.Label()
label.set_markup("<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.set_active(self.build_toolchain)
toolcheck.connect("toggled", self.toggle_toolchain_cb)
hbox.pack_start(toolcheck, expand=False, fill=False, padding=6)
hbox = gtk.HBox(False, 12)
hbox.show()
pbox.pack_start(hbox, expand=False, fill=False, padding=6)
label = gtk.Label("Toolchain host:")
label.show()
hbox.pack_start(label, expand=False, fill=False, padding=6)
self.sdk_machine_combo = gtk.combo_box_new_text()
self.sdk_machine_combo.set_tooltip_text("Select the host architecture of the external machine")
self.sdk_machine_combo.show()
hbox.pack_start(self.sdk_machine_combo, expand=False, fill=False, padding=6)
# headerscheck = gtk.CheckButton("Include development headers with toolchain")
# headerscheck.show()
# headerscheck.set_active(self.build_toolchain_headers)
# headerscheck.connect("toggled", self.toggle_headers_cb)
# hbox.pack_start(headerscheck, expand=False, fill=False, padding=6)
self.connect("response", self.prefs_response_cb)
@@ -0,0 +1,153 @@
#
# 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
from bb.ui.crumbs.hig import CrumbsDialog
class LayerEditor(gtk.Dialog):
"""
Gtk+ Widget for enabling and disabling layers.
Layers are added through using an open dialog to find the layer.conf
Disabled layers are deleted from conf/bblayers.conf
"""
def __init__(self, configurator, parent=None):
gtk.Dialog.__init__(self, "Layers", None,
gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
# We want to show a little more of the treeview in the default,
# emptier, case
self.set_size_request(-1, 300)
self.set_border_width(6)
self.vbox.set_property("spacing", 0)
self.action_area.set_property("border-width", 6)
self.configurator = configurator
self.newly_added = {}
# Label to inform users that meta is enabled but that you can't
# disable it as it'd be a *bad* idea
msg = "As the core of the build system the <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):
def conf_error(parent, lbl):
dialog = CrumbsDialog(parent, lbl)
dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
response = dialog.run()
dialog.destroy()
dialog = gtk.FileChooserDialog("Add new layer", parent,
gtk.FILE_CHOOSER_ACTION_OPEN,
(gtk.STOCK_CANCEL, gtk.RESPONSE_NO,
gtk.STOCK_OPEN, gtk.RESPONSE_YES))
label = gtk.Label("Select the layer.conf of the layer you wish to add")
label.show()
dialog.set_extra_widget(label)
response = dialog.run()
path = dialog.get_filename()
dialog.destroy()
lbl = "<b>Error</b>\nUnable to load layer <i>%s</i> because " % path
if response == gtk.RESPONSE_YES:
# FIXME: verify we've actually got a layer conf?
if path.endswith("layer.conf"):
name, layerpath = self.configurator.addLayerConf(path)
if name and layerpath:
self.newly_added[name] = layerpath
self.layer_store.append([name, layerpath, True])
return
elif name:
return
else:
lbl += "there was a problem parsing the layer.conf."
else:
lbl += "it is not a layer.conf file."
conf_error(parent, lbl)
@@ -0,0 +1,20 @@
import gtk
class ProgressBar(gtk.Dialog):
def __init__(self, parent):
gtk.Dialog.__init__(self, flags=(gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT))
self.set_title("Parsing metadata, please wait...")
self.set_default_size(500, 0)
self.set_transient_for(parent)
self.progress = gtk.ProgressBar()
self.vbox.pack_start(self.progress)
self.show_all()
def update(self, x, y):
self.progress.set_fraction(float(x)/float(y))
self.progress.set_text("%2d %%" % (x*100/y))
def pulse(self):
self.progress.set_text("Loading...")
self.progress.pulse()
@@ -0,0 +1,606 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.5 on Mon Nov 10 12:24:12 2008 -->
<glade-interface>
<widget class="GtkDialog" id="build_dialog">
<property name="title" translatable="yes">Start a build</property>
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox1">
<property name="visible">True</property>
<property name="spacing">2</property>
<child>
<widget class="GtkTable" id="build_table">
<property name="visible">True</property>
<property name="border_width">6</property>
<property name="n_rows">7</property>
<property name="n_columns">3</property>
<property name="column_spacing">5</property>
<property name="row_spacing">6</property>
<child>
<widget class="GtkAlignment" id="status_alignment">
<property name="visible">True</property>
<property name="left_padding">12</property>
<child>
<widget class="GtkHBox" id="status_hbox">
<property name="spacing">6</property>
<child>
<widget class="GtkImage" id="status_image">
<property name="visible">True</property>
<property name="no_show_all">True</property>
<property name="xalign">0</property>
<property name="stock">gtk-dialog-error</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="status_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">If you see this text something is wrong...</property>
<property name="use_markup">True</property>
<property name="use_underline">True</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
</child>
</widget>
<packing>
<property name="right_attach">3</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Build configuration&lt;/b&gt;</property>
<property name="use_markup">True</property>
</widget>
<packing>
<property name="right_attach">3</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkComboBox" id="image_combo">
<property name="visible">True</property>
<property name="sensitive">False</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="image_label">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Image:</property>
</widget>
<packing>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkComboBox" id="distribution_combo">
<property name="visible">True</property>
<property name="sensitive">False</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="distribution_label">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Distribution:</property>
</widget>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkComboBox" id="machine_combo">
<property name="visible">True</property>
<property name="sensitive">False</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="machine_label">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Machine:</property>
</widget>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkButton" id="refresh_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">gtk-refresh</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
</widget>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="location_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">32</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Location:</property>
</widget>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Repository&lt;/b&gt;</property>
<property name="use_markup">True</property>
</widget>
<packing>
<property name="right_attach">3</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment3">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
<property name="y_options"></property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area1">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
</widget>
</child>
</widget>
<widget class="GtkDialog" id="dialog2">
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox2">
<property name="visible">True</property>
<property name="spacing">2</property>
<child>
<widget class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="border_width">6</property>
<property name="n_rows">7</property>
<property name="n_columns">3</property>
<property name="column_spacing">6</property>
<property name="row_spacing">6</property>
<child>
<widget class="GtkLabel" id="label7">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Repositories&lt;/b&gt;</property>
<property name="use_markup">True</property>
</widget>
<packing>
<property name="right_attach">3</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment4">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="left_padding">12</property>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<child>
<widget class="GtkTreeView" id="treeview1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_clickable">True</property>
</widget>
</child>
</widget>
</child>
</widget>
<packing>
<property name="right_attach">3</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="entry1">
<property name="visible">True</property>
<property name="can_focus">True</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">3</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label9">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Additional packages&lt;/b&gt;</property>
<property name="use_markup">True</property>
</widget>
<packing>
<property name="right_attach">3</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment6">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xscale">0</property>
<child>
<widget class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Location: </property>
</widget>
</child>
</widget>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment7">
<property name="visible">True</property>
<property name="xalign">1</property>
<property name="xscale">0</property>
<child>
<widget class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="spacing">5</property>
<child>
<widget class="GtkButton" id="button7">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">gtk-remove</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
</widget>
</child>
<child>
<widget class="GtkButton" id="button6">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">gtk-edit</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="button5">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">gtk-add</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
</widget>
<packing>
<property name="position">2</property>
</packing>
</child>
</widget>
</child>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">3</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment5">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label10">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">Search:</property>
</widget>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="entry2">
<property name="visible">True</property>
<property name="can_focus">True</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">3</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment8">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="left_padding">12</property>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<child>
<widget class="GtkTreeView" id="treeview2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_clickable">True</property>
</widget>
</child>
</widget>
</child>
</widget>
<packing>
<property name="right_attach">3</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
<property name="y_options"></property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area2">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="button4">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">gtk-close</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
</widget>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
</widget>
</child>
</widget>
<widget class="GtkWindow" id="main_window">
<child>
<widget class="GtkVBox" id="main_window_vbox">
<property name="visible">True</property>
<child>
<widget class="GtkToolbar" id="main_toolbar">
<property name="visible">True</property>
<child>
<widget class="GtkToolButton" id="main_toolbutton_build">
<property name="visible">True</property>
<property name="label" translatable="yes">Build</property>
<property name="stock_id">gtk-execute</property>
</widget>
<packing>
<property name="expand">False</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<widget class="GtkVPaned" id="vpaned1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<widget class="GtkScrolledWindow" id="results_scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<widget class="GtkScrolledWindow" id="progress_scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>
@@ -0,0 +1,369 @@
#
# BitBake Graphical GTK User Interface
#
# Copyright (C) 2008 Intel Corporation
#
# Authored by Rob Bradford <rob@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
import gobject
import logging
import time
import urllib
import urllib2
class Colors(object):
OK = "#ffffff"
RUNNING = "#aaffaa"
WARNING ="#f88017"
ERROR = "#ffaaaa"
class RunningBuildModel (gtk.TreeStore):
(COL_LOG, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_COLOR, COL_NUM_ACTIVE) = range(7)
def __init__ (self):
gtk.TreeStore.__init__ (self,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_INT)
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,
())
}
pids_to_task = {}
tasks_to_iter = {}
def __init__ (self, sequential=False):
gobject.GObject.__init__ (self)
self.model = RunningBuildModel()
self.sequential = sequential
def reset (self):
self.pids_to_task.clear()
self.tasks_to_iter.clear()
self.model.clear()
def handle_event (self, event, pbar=None):
# Handle an event from the event queue, this may result in updating
# the model and thus the UI. Or it may be to tell us that the build
# has finished successfully (or not, as the case may be.)
parent = None
pid = 0
package = None
task = None
# If we have a pid attached to this message/event try and get the
# (package, task) pair for it. If we get that then get the parent iter
# for the message.
if hasattr(event, 'pid'):
pid = event.pid
if hasattr(event, 'process'):
pid = event.process
if pid and pid in self.pids_to_task:
(package, task) = self.pids_to_task[pid]
parent = self.tasks_to_iter[(package, task)]
if(isinstance(event, logging.LogRecord)):
# FIXME: this is a hack! More info in Yocto #1433
# http://bugzilla.pokylinux.org/show_bug.cgi?id=1433, temporarily
# mask the error message as it's not informative for the user.
if event.msg.startswith("Execution of event handler 'run_buildstats' failed"):
return
if (event.levelno < logging.INFO or
event.msg.startswith("Running task")):
return # don't add these to the list
if event.levelno >= logging.ERROR:
icon = "dialog-error"
color = Colors.ERROR
elif event.levelno >= logging.WARNING:
icon = "dialog-warning"
color = Colors.WARNING
else:
icon = None
color = Colors.OK
# if we know which package we belong to, we'll append onto its list.
# otherwise, we'll jump to the top of the master list
if self.sequential or not parent:
tree_add = self.model.append
else:
tree_add = self.model.prepend
tree_add(parent,
(None,
package,
task,
event.getMessage(),
icon,
color,
0))
elif isinstance(event, bb.build.TaskStarted):
(package, task) = (event._package, event._task)
# Save out this PID.
self.pids_to_task[pid] = (package, task)
# Check if we already have this package in our model. If so then
# that can be the parent for the task. Otherwise we create a new
# top level for the package.
if ((package, None) in self.tasks_to_iter):
parent = self.tasks_to_iter[(package, None)]
else:
if self.sequential:
add = self.model.append
else:
add = self.model.prepend
parent = add(None, (None,
package,
None,
"Package: %s" % (package),
None,
Colors.OK,
0))
self.tasks_to_iter[(package, None)] = parent
# Because this parent package now has an active child mark it as
# such.
# @todo if parent is already in error, don't mark it green
self.model.set(parent, self.model.COL_ICON, "gtk-execute",
self.model.COL_COLOR, Colors.RUNNING)
# Add an entry in the model for this task
i = self.model.append (parent, (None,
package,
task,
"Task: %s" % (task),
"gtk-execute",
Colors.RUNNING,
0))
# update the parent's active task count
num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] + 1
self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
# Save out the iter so that we can find it when we have a message
# that we need to attach to a task.
self.tasks_to_iter[(package, task)] = i
# If we don't handle these the GUI does not proceed
elif isinstance(event, bb.build.TaskInvalid):
return
elif isinstance(event, bb.build.TaskBase):
current = self.tasks_to_iter[(package, task)]
parent = self.tasks_to_iter[(package, None)]
# remove this task from the parent's active count
num_active = self.model.get(parent, self.model.COL_NUM_ACTIVE)[0] - 1
self.model.set(parent, self.model.COL_NUM_ACTIVE, num_active)
if isinstance(event, bb.build.TaskFailed):
# Mark the task and parent as failed
icon = "dialog-error"
color = Colors.ERROR
logfile = event.logfile
if logfile and os.path.exists(logfile):
with open(logfile) as f:
logdata = f.read()
self.model.append(current, ('pastebin', None, None, logdata, 'gtk-error', Colors.OK, 0))
for i in (current, parent):
self.model.set(i, self.model.COL_ICON, icon,
self.model.COL_COLOR, color)
else:
icon = None
color = Colors.OK
# Mark the task as inactive
self.model.set(current, self.model.COL_ICON, icon,
self.model.COL_COLOR, color)
# Mark the parent package as inactive, but make sure to
# preserve error and active states
i = self.tasks_to_iter[(package, None)]
if self.model.get(parent, self.model.COL_ICON) != 'dialog-error':
self.model.set(parent, self.model.COL_ICON, icon)
if num_active == 0:
self.model.set(parent, self.model.COL_COLOR, Colors.OK)
# Clear the iters and the pids since when the task goes away the
# pid will no longer be used for messages
del self.tasks_to_iter[(package, task)]
del self.pids_to_task[pid]
elif isinstance(event, bb.event.BuildStarted):
self.emit("build-started")
self.model.prepend(None, (None,
None,
None,
"Build Started (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
None,
Colors.OK,
0))
elif isinstance(event, bb.event.BuildCompleted):
failures = int (event._failures)
self.model.prepend(None, (None,
None,
None,
"Build Completed (%s)" % time.strftime('%m/%d/%Y %H:%M:%S'),
None,
Colors.OK,
0))
# Emit the appropriate signal depending on the number of failures
if (failures >= 1):
self.emit ("build-failed")
else:
self.emit ("build-succeeded")
# Emit a generic "build-complete" signal for things wishing to
# handle when the build is finished
self.emit("build-complete")
elif isinstance(event, bb.command.CommandFailed):
if event.error.startswith("Exited with"):
# If the command fails with an exit code we're done, emit the
# generic signal for the UI to notify the user
self.emit("build-complete")
elif isinstance(event, bb.event.CacheLoadStarted) and pbar:
pbar.set_title("Loading cache")
self.progress_total = event.total
pbar.update(0, self.progress_total)
elif isinstance(event, bb.event.CacheLoadProgress) and pbar:
pbar.update(event.current, self.progress_total)
elif isinstance(event, bb.event.CacheLoadCompleted) and pbar:
pbar.update(self.progress_total, self.progress_total)
pbar.hide()
elif isinstance(event, bb.event.ParseStarted) and pbar:
if event.total == 0:
return
pbar.set_title("Processing recipes")
self.progress_total = event.total
pbar.update(0, self.progress_total)
elif isinstance(event, bb.event.ParseProgress) and pbar:
pbar.update(event.current, self.progress_total)
elif isinstance(event, bb.event.ParseCompleted) and pbar:
pbar.hide()
return
def do_pastebin(text):
url = 'http://pastebin.com/api_public.php'
params = {'paste_code': text, 'paste_format': 'text'}
req = urllib2.Request(url, urllib.urlencode(params))
response = urllib2.urlopen(req)
paste_url = response.read()
return paste_url
class RunningBuildTreeView (gtk.TreeView):
__gsignals__ = {
"button_press_event" : "override"
}
def __init__ (self, readonly=False):
gtk.TreeView.__init__ (self)
self.readonly = readonly
# The icon that indicates whether we're building or failed.
renderer = gtk.CellRendererPixbuf ()
col = gtk.TreeViewColumn ("Status", renderer)
col.add_attribute (renderer, "icon-name", 4)
self.append_column (col)
# The message of the build.
self.message_renderer = gtk.CellRendererText ()
self.message_column = gtk.TreeViewColumn ("Message", self.message_renderer, text=3)
self.message_column.add_attribute(self.message_renderer, 'background', 5)
self.message_renderer.set_property('editable', (not self.readonly))
self.append_column (self.message_column)
def do_button_press_event(self, event):
gtk.TreeView.do_button_press_event(self, event)
if event.button == 3:
selection = super(RunningBuildTreeView, self).get_selection()
(model, it) = selection.get_selected()
if it is not None:
can_paste = model.get(it, model.COL_LOG)[0]
if can_paste == 'pastebin':
# build a simple menu with a pastebin option
menu = gtk.Menu()
menuitem = gtk.MenuItem("Copy")
menu.append(menuitem)
menuitem.connect("activate", self.clipboard_handler, (model, it))
menuitem.show()
menuitem = gtk.MenuItem("Send log to pastebin")
menu.append(menuitem)
menuitem.connect("activate", self.pastebin_handler, (model, it))
menuitem.show()
menu.show()
menu.popup(None, None, None, event.button, event.time)
def _add_to_clipboard(self, clipping):
"""
Add the contents of clipping to the system clipboard.
"""
clipboard = gtk.clipboard_get()
clipboard.set_text(clipping)
clipboard.store()
def pastebin_handler(self, widget, data):
"""
Send the log data to pastebin, then add the new paste url to the
clipboard.
"""
(model, it) = data
paste_url = do_pastebin(model.get(it, model.COL_MESSAGE)[0])
# @todo Provide visual feedback to the user that it is done and that
# it worked.
print paste_url
self._add_to_clipboard(paste_url)
def clipboard_handler(self, widget, data):
"""
"""
(model, it) = data
message = model.get(it, model.COL_MESSAGE)[0]
self._add_to_clipboard(message)
@@ -0,0 +1,620 @@
#
# 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
import gobject
import re
class BuildRep(gobject.GObject):
def __init__(self, userpkgs, allpkgs, base_image=None):
gobject.GObject.__init__(self)
self.base_image = base_image
self.allpkgs = allpkgs
self.userpkgs = userpkgs
def loadRecipe(self, pathname):
contents = []
packages = ""
base_image = ""
with open(pathname, 'r') as f:
contents = f.readlines()
pkg_pattern = "^\s*(IMAGE_INSTALL)\s*([+=.?]+)\s*(\".*?\")"
img_pattern = "^\s*(require)\s+(\S+.bb)"
for line in contents:
matchpkg = re.search(pkg_pattern, line)
matchimg = re.search(img_pattern, line)
if matchpkg:
packages = packages + matchpkg.group(3).strip('"')
if matchimg:
base_image = os.path.basename(matchimg.group(2)).split(".")[0]
self.base_image = base_image
self.userpkgs = packages
def writeRecipe(self, writepath, model):
template = """
# Recipe generated by the HOB
require %s
IMAGE_INSTALL += "%s"
"""
empty_template = """
# Recipe generated by the HOB
inherit core-image
IMAGE_INSTALL = "%s"
"""
if self.base_image and not self.base_image == "empty":
meta_path = model.find_image_path(self.base_image)
recipe = template % (meta_path, self.userpkgs)
else:
recipe = empty_template % self.allpkgs
if os.path.exists(writepath):
os.rename(writepath, "%s~" % writepath)
with open(writepath, 'w') as r:
r.write(recipe)
return writepath
class TaskListModel(gtk.ListStore):
"""
This class defines an gtk.ListStore subclass which will convert the output
of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also
providing convenience functions to access gtk.TreeModel subclasses which
provide filtered views of the data.
"""
(COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC, COL_IMG, COL_PATH, COL_PN) = range(11)
__gsignals__ = {
"tasklist-populated" : (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
()),
"contents-changed" : (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gobject.TYPE_INT,)),
"image-changed" : (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gobject.TYPE_STRING,)),
}
"""
"""
def __init__(self):
self.contents = None
self.tasks = None
self.packages = None
self.images = None
self.selected_image = None
gtk.ListStore.__init__ (self,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_BOOLEAN,
gobject.TYPE_BOOLEAN,
gobject.TYPE_STRING,
gobject.TYPE_STRING)
"""
Helper method to determine whether name is a target pn
"""
def non_target_name(self, name):
if ('-native' in name) or ('-cross' in name) or name.startswith('virtual/'):
return True
return False
def contents_changed_cb(self, tree_model, path, it=None):
pkg_cnt = self.contents.iter_n_children(None)
self.emit("contents-changed", pkg_cnt)
def contents_model_filter(self, model, it):
if not model.get_value(it, self.COL_INC) or model.get_value(it, self.COL_TYPE) == 'image':
return False
name = model.get_value(it, self.COL_NAME)
if self.non_target_name(name):
return False
else:
return True
"""
Create, if required, and return a filtered gtk.TreeModel
containing only the items which are to be included in the
image
"""
def contents_model(self):
if not self.contents:
self.contents = self.filter_new()
self.contents.set_visible_func(self.contents_model_filter)
self.contents.connect("row-inserted", self.contents_changed_cb)
self.contents.connect("row-deleted", self.contents_changed_cb)
return self.contents
"""
Helper function to determine whether an item is a task
"""
def task_model_filter(self, model, it):
if model.get_value(it, self.COL_TYPE) == 'task':
return True
else:
return False
"""
Create, if required, and return a filtered gtk.TreeModel
containing only the items which are tasks
"""
def tasks_model(self):
if not self.tasks:
self.tasks = self.filter_new()
self.tasks.set_visible_func(self.task_model_filter)
return self.tasks
"""
Helper function to determine whether an item is an image
"""
def image_model_filter(self, model, it):
if model.get_value(it, self.COL_TYPE) == 'image':
return True
else:
return False
"""
Create, if required, and return a filtered gtk.TreeModel
containing only the items which are images
"""
def images_model(self):
if not self.images:
self.images = self.filter_new()
self.images.set_visible_func(self.image_model_filter)
return self.images
"""
Helper function to determine whether an item is a package
"""
def package_model_filter(self, model, it):
if model.get_value(it, self.COL_TYPE) != 'package':
return False
else:
name = model.get_value(it, self.COL_NAME)
if self.non_target_name(name):
return False
return True
"""
Create, if required, and return a filtered gtk.TreeModel
containing only the items which are packages
"""
def packages_model(self):
if not self.packages:
self.packages = self.filter_new()
self.packages.set_visible_func(self.package_model_filter)
return self.packages
"""
The populate() function takes as input the data from a
bb.event.TargetsTreeGenerated event and populates the TaskList.
Once the population is done it emits gsignal tasklist-populated
to notify any listeners that the model is ready
"""
def populate(self, event_model):
# First clear the model, in case repopulating
self.clear()
for item in event_model["pn"]:
atype = 'package'
name = item
summary = event_model["pn"][item]["summary"]
lic = event_model["pn"][item]["license"]
group = event_model["pn"][item]["section"]
filename = event_model["pn"][item]["filename"]
if ('task-' in name):
atype = 'task'
elif ('-image-' in name):
atype = 'image'
# Create a combined list of build and runtime dependencies and
# then remove any duplicate entries and any entries for -dev
# packages
depends = event_model["depends"].get(item, [])
rdepends = event_model["rdepends-pn"].get(item, [])
packages = {}
for pkg in event_model["packages"]:
if event_model["packages"][pkg]["pn"] == name:
deps = []
deps.extend(depends)
deps.extend(event_model["rdepends-pkg"].get(pkg, []))
deps.extend(rdepends)
deps = self.squish(deps)
# rdepends-pn includes pn-dev
if ("%s-dev" % item) in deps:
deps.remove("%s-dev" % item)
# rdepends-on includes pn
if pkg in deps:
deps.remove(pkg)
packages[pkg] = deps
for p in packages:
self.set(self.append(), self.COL_NAME, p, self.COL_DESC, summary,
self.COL_LIC, lic, self.COL_GROUP, group,
self.COL_DEPS, " ".join(packages[p]), self.COL_BINB, "",
self.COL_TYPE, atype, self.COL_INC, False,
self.COL_IMG, False, self.COL_PATH, filename,
self.COL_PN, item)
self.emit("tasklist-populated")
"""
Load a BuildRep into the model
"""
def load_image_rep(self, rep):
# Unset everything
it = self.get_iter_first()
while it:
path = self.get_path(it)
self[path][self.COL_INC] = False
self[path][self.COL_IMG] = False
it = self.iter_next(it)
# Iterate the images and disable them all
it = self.images.get_iter_first()
while it:
path = self.images.convert_path_to_child_path(self.images.get_path(it))
name = self[path][self.COL_NAME]
if name == rep.base_image:
self.include_item(path, image_contents=True)
else:
self[path][self.COL_INC] = False
it = self.images.iter_next(it)
# Mark all of the additional packages for inclusion
packages = rep.userpkgs.split(" ")
it = self.get_iter_first()
while it:
path = self.get_path(it)
name = self[path][self.COL_NAME]
if name in packages:
self.include_item(path, binb="User Selected")
packages.remove(name)
it = self.iter_next(it)
self.emit("image-changed", rep.base_image)
"""
squish lst so that it doesn't contain any duplicate entries
"""
def squish(self, lst):
seen = {}
for l in lst:
seen[l] = 1
return seen.keys()
"""
Mark the item at path as not included
NOTE:
path should be a gtk.TreeModelPath into self (not a filtered model)
"""
def remove_item_path(self, path):
self[path][self.COL_BINB] = ""
self[path][self.COL_INC] = False
"""
Recursively called to mark the item at opath and any package which
depends on it for removal.
NOTE: This method dumbly removes user selected packages and since we don't
do significant reverse dependency tracking it's easier and simpler to save
the items marked as user selected and re-add them once the removal sweep is
complete.
"""
def mark(self, opath):
usersel = {}
removed = []
it = self.get_iter_first()
# The name of the item we're removing, so that we can use it to find
# other items which either depend on it, or were brought in by it
marked_name = self[opath][self.COL_NAME]
# Remove the passed item
self.remove_item_path(opath)
# Remove all dependent packages, update binb
while it:
path = self.get_path(it)
it = self.iter_next(it)
inc = self[path][self.COL_INC]
deps = self[path][self.COL_DEPS]
binb = self[path][self.COL_BINB].split(', ')
itype = self[path][self.COL_TYPE]
itname = self[path][self.COL_NAME]
# We ignore anything that isn't a package
if not itype == "package":
continue
# If the user added this item and it's not the item we're removing
# we should keep it and its dependencies, the easiest way to do so
# is to save its name and re-mark it for inclusion once dependency
# processing is complete
if "User Selected" in binb:
usersel[itname] = self[path][self.COL_IMG]
# If the iterated item is included and depends on the removed
# item it should also be removed.
# FIXME: need to ensure partial name matching doesn't happen
if inc and marked_name in deps and itname not in removed:
# found a dependency, remove it
removed.append(itname)
self.mark(path)
# If the iterated item was brought in by the removed (passed) item
# try and find an alternative dependee and update the binb column
if inc and marked_name in binb:
binb.remove(marked_name)
self[path][self.COL_BINB] = ', '.join(binb).lstrip(', ')
# Re-add any removed user selected items
for u in usersel:
npath = self.find_path_for_item(u)
self.include_item(item_path=npath,
binb="User Selected",
image_contents=usersel[u])
"""
Remove items from contents if the have an empty COL_BINB (brought in by)
caused by all packages they are a dependency of being removed.
If the item isn't a package we leave it included.
"""
def sweep_up(self):
it = self.contents.get_iter_first()
while it:
binb = self.contents.get_value(it, self.COL_BINB)
itype = self.contents.get_value(it, self.COL_TYPE)
remove = False
if itype == 'package' and not binb:
oit = self.contents.convert_iter_to_child_iter(it)
opath = self.get_path(oit)
self.mark(opath)
remove = True
# When we remove a package from the contents model we alter the
# model, so continuing to iterate is bad. *Furthermore* it's
# likely that the removal has affected an already iterated item
# so we should start from the beginning anyway.
# Only when we've managed to iterate the entire contents model
# without removing any items do we allow the loop to exit.
if remove:
it = self.contents.get_iter_first()
else:
it = self.contents.iter_next(it)
"""
Check whether the item at item_path is included or not
"""
def contents_includes_path(self, item_path):
return self[item_path][self.COL_INC]
"""
Add this item, and any of its dependencies, to the image contents
"""
def include_item(self, item_path, binb="", image_contents=False):
item_name = self[item_path][self.COL_NAME]
item_deps = self[item_path][self.COL_DEPS]
self[item_path][self.COL_INC] = True
item_bin = self[item_path][self.COL_BINB].split(', ')
if binb and not binb in item_bin:
item_bin.append(binb)
self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ')
# We want to do some magic with things which are brought in by the
# base image so tag them as so
if image_contents:
self[item_path][self.COL_IMG] = True
if self[item_path][self.COL_TYPE] == 'image':
self.selected_image = item_name
if item_deps:
# Ensure all of the items deps are included and, where appropriate,
# add this item to their COL_BINB
for dep in item_deps.split(" "):
# If the contents model doesn't already contain dep, add it
dep_path = self.find_path_for_item(dep)
if not dep_path:
continue
dep_included = self.contents_includes_path(dep_path)
if dep_included and not dep in item_bin:
# don't set the COL_BINB to this item if the target is an
# item in our own COL_BINB
dep_bin = self[dep_path][self.COL_BINB].split(', ')
if not item_name in dep_bin:
dep_bin.append(item_name)
self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')
elif not dep_included:
self.include_item(dep_path, binb=item_name, image_contents=image_contents)
"""
Find the model path for the item_name
Returns the path in the model or None
"""
def find_path_for_item(self, item_name):
# We don't include virtual/* or *-native items in the model so save a
# heavy iteration loop by exiting early for these items
if self.non_target_name(item_name):
return None
it = self.get_iter_first()
while it:
if (self.get_value(it, self.COL_NAME) == item_name):
return self.get_path(it)
else:
it = self.iter_next(it)
return None
"""
Empty self.contents by setting the include of each entry to None
"""
def reset(self):
# Deselect images - slightly more complex logic so that we don't
# have to iterate all of the contents of the main model, instead
# just iterate the images model.
if self.selected_image:
iit = self.images.get_iter_first()
while iit:
pit = self.images.convert_iter_to_child_iter(iit)
self.set(pit, self.COL_INC, False)
iit = self.images.iter_next(iit)
self.selected_image = None
it = self.contents.get_iter_first()
while it:
oit = self.contents.convert_iter_to_child_iter(it)
self.set(oit,
self.COL_INC, False,
self.COL_BINB, "",
self.COL_IMG, False)
# As we've just removed the first item...
it = self.contents.get_iter_first()
"""
Returns two lists. One of user selected packages and the other containing
all selected packages
"""
def get_selected_packages(self):
allpkgs = []
userpkgs = []
it = self.contents.get_iter_first()
while it:
sel = "User Selected" in self.contents.get_value(it, self.COL_BINB)
name = self.contents.get_value(it, self.COL_NAME)
allpkgs.append(name)
if sel:
userpkgs.append(name)
it = self.contents.iter_next(it)
return userpkgs, allpkgs
"""
Return a squished (uniquified) list of the PN's of all selected items
"""
def get_selected_pn(self):
pns = []
it = self.contents.get_iter_first()
while it:
if self.contents.get_value(it, self.COL_BINB):
pns.append(self.contents.get_value(it, self.COL_PN))
it = self.contents.iter_next(it)
return self.squish(pns)
def image_contents_removed(self):
it = self.get_iter_first()
while it:
sel = self.get_value(it, self.COL_INC)
img = self.get_value(it, self.COL_IMG)
if img and not sel:
return True
it = self.iter_next(it)
return False
def get_build_rep(self):
userpkgs, allpkgs = self.get_selected_packages()
# If base image contents have been removed start from an empty rootfs
if not self.selected_image or self.image_contents_removed():
image = "empty"
else:
image = self.selected_image
return BuildRep(" ".join(userpkgs), " ".join(allpkgs), image)
def find_reverse_depends(self, pn):
revdeps = []
it = self.contents.get_iter_first()
while it:
name = self.contents.get_value(it, self.COL_NAME)
itype = self.contents.get_value(it, self.COL_TYPE)
deps = self.contents.get_value(it, self.COL_DEPS)
it = self.contents.iter_next(it)
if not itype == 'package':
continue
if pn in deps:
revdeps.append(name)
if pn in revdeps:
revdeps.remove(pn)
return revdeps
def set_selected_image(self, img):
self.selected_image = img
path = self.find_path_for_item(img)
self.include_item(item_path=path,
binb="User Selected",
image_contents=True)
self.emit("image-changed", self.selected_image)
def set_selected_packages(self, pkglist):
selected = pkglist
it = self.get_iter_first()
while it:
name = self.get_value(it, self.COL_NAME)
if name in pkglist:
pkglist.remove(name)
path = self.get_path(it)
self.include_item(item_path=path,
binb="User Selected")
if len(pkglist) == 0:
return
it = self.iter_next(it)
def find_image_path(self, image):
it = self.images.get_iter_first()
while it:
image_name = self.images.get_value(it, self.COL_NAME)
if image_name == image:
path = self.images.get_value(it, self.COL_PATH)
meta_pattern = "(\S*)/(meta*/)(\S*)"
meta_match = re.search(meta_pattern, path)
if meta_match:
_, lyr, bbrel = path.partition(meta_match.group(2))
if bbrel:
path = bbrel
return path
it = self.images.iter_next(it)