1
0
mirror of https://git.yoctoproject.org/poky synced 2026-06-02 01:19:52 +00:00

bitbake: Switch to bitbake-dev version (bitbake master upstream)

Signed-off-by: Richard Purdie <rpurdie@linux.intel.com>
This commit is contained in:
Richard Purdie
2010-01-20 18:46:02 +00:00
parent 1bfd6edef9
commit 22c29d8651
83 changed files with 1398 additions and 13821 deletions
+18
View File
@@ -0,0 +1,18 @@
#
# BitBake UI Implementation
#
# 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.
+18
View File
@@ -0,0 +1,18 @@
#
# BitBake UI Implementation
#
# 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.
+457
View File
@@ -0,0 +1,457 @@
#
# 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):
f = open (filename, "r")
conf = BuildConfiguration()
for line in f.readlines():
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
f.close ()
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, 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)
+606
View File
@@ -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>
+180
View File
@@ -0,0 +1,180 @@
#
# 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
class RunningBuildModel (gtk.TreeStore):
(COL_TYPE, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_ACTIVE) = (0, 1, 2, 3, 4, 5)
def __init__ (self):
gtk.TreeStore.__init__ (self,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_BOOLEAN)
class RunningBuild (gobject.GObject):
__gsignals__ = {
'build-succeeded' : (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
()),
'build-failed' : (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
())
}
pids_to_task = {}
tasks_to_iter = {}
def __init__ (self):
gobject.GObject.__init__ (self)
self.model = RunningBuildModel()
def handle_event (self, event):
# 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 hassattr(event, 'pid'):
pid = event.pid
if self.pids_to_task.has_key(pid):
(package, task) = self.pids_to_task[pid]
parent = self.tasks_to_iter[(package, task)]
if isinstance(event, bb.msg.Msg):
# Set a pretty icon for the message based on it's type.
if isinstance(event, bb.msg.MsgWarn):
icon = "dialog-warning"
elif isinstance(event, bb.msg.MsgErr):
icon = "dialog-error"
else:
icon = None
# Ignore the "Running task i of n .." messages
if (event._message.startswith ("Running task")):
return
# Add the message to the tree either at the top level if parent is
# None otherwise as a descendent of a task.
self.model.append (parent,
(event.__name__.split()[-1], # e.g. MsgWarn, MsgError
package,
task,
event._message,
icon,
False))
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 (self.tasks_to_iter.has_key ((package, None))):
parent = self.tasks_to_iter[(package, None)]
else:
parent = self.model.append (None, (None,
package,
None,
"Package: %s" % (package),
None,
False))
self.tasks_to_iter[(package, None)] = parent
# Because this parent package now has an active child mark it as
# such.
self.model.set(parent, self.model.COL_ICON, "gtk-execute")
# Add an entry in the model for this task
i = self.model.append (parent, (None,
package,
task,
"Task: %s" % (task),
None,
False))
# 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
# Mark this task as active.
self.model.set(i, self.model.COL_ICON, "gtk-execute")
elif isinstance(event, bb.build.Task):
if isinstance(event, bb.build.TaskFailed):
# Mark the task as failed
i = self.tasks_to_iter[(package, task)]
self.model.set(i, self.model.COL_ICON, "dialog-error")
# Mark the parent package as failed
i = self.tasks_to_iter[(package, None)]
self.model.set(i, self.model.COL_ICON, "dialog-error")
else:
# Mark the task as inactive
i = self.tasks_to_iter[(package, task)]
self.model.set(i, self.model.COL_ICON, None)
# Mark the parent package as inactive
i = self.tasks_to_iter[(package, None)]
self.model.set(i, self.model.COL_ICON, None)
# 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.BuildCompleted):
failures = int (event._failures)
# Emit the appropriate signal depending on the number of failures
if (failures > 1):
self.emit ("build-failed")
else:
self.emit ("build-succeeded")
class RunningBuildTreeView (gtk.TreeView):
def __init__ (self):
gtk.TreeView.__init__ (self)
# 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.
renderer = gtk.CellRendererText ()
col = gtk.TreeViewColumn ("Message", renderer, text=3)
self.append_column (col)
+272
View File
@@ -0,0 +1,272 @@
#
# BitBake Graphical GTK based Dependency Explorer
#
# Copyright (C) 2007 Ross Burton
# Copyright (C) 2007 - 2008 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.
import gobject
import gtk
import threading
import xmlrpclib
# Package Model
(COL_PKG_NAME) = (0)
# Dependency Model
(TYPE_DEP, TYPE_RDEP) = (0, 1)
(COL_DEP_TYPE, COL_DEP_PARENT, COL_DEP_PACKAGE) = (0, 1, 2)
class PackageDepView(gtk.TreeView):
def __init__(self, model, dep_type, label):
gtk.TreeView.__init__(self)
self.current = None
self.dep_type = dep_type
self.filter_model = model.filter_new()
self.filter_model.set_visible_func(self._filter)
self.set_model(self.filter_model)
#self.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PACKAGE))
def _filter(self, model, iter):
(this_type, package) = model.get(iter, COL_DEP_TYPE, COL_DEP_PARENT)
if this_type != self.dep_type: return False
return package == self.current
def set_current_package(self, package):
self.current = package
self.filter_model.refilter()
class PackageReverseDepView(gtk.TreeView):
def __init__(self, model, label):
gtk.TreeView.__init__(self)
self.current = None
self.filter_model = model.filter_new()
self.filter_model.set_visible_func(self._filter)
self.set_model(self.filter_model)
self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PARENT))
def _filter(self, model, iter):
package = model.get_value(iter, COL_DEP_PACKAGE)
return package == self.current
def set_current_package(self, package):
self.current = package
self.filter_model.refilter()
class DepExplorer(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
self.set_title("Dependency Explorer")
self.set_default_size(500, 500)
self.connect("delete-event", gtk.main_quit)
# Create the data models
self.pkg_model = gtk.ListStore(gobject.TYPE_STRING)
self.depends_model = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING)
pane = gtk.HPaned()
pane.set_position(250)
self.add(pane)
# The master list of packages
scrolled = gtk.ScrolledWindow()
scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolled.set_shadow_type(gtk.SHADOW_IN)
self.pkg_treeview = gtk.TreeView(self.pkg_model)
self.pkg_treeview.get_selection().connect("changed", self.on_cursor_changed)
self.pkg_treeview.append_column(gtk.TreeViewColumn("Package", gtk.CellRendererText(), text=COL_PKG_NAME))
pane.add1(scrolled)
scrolled.add(self.pkg_treeview)
box = gtk.VBox(homogeneous=True, spacing=4)
# Runtime Depends
scrolled = gtk.ScrolledWindow()
scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolled.set_shadow_type(gtk.SHADOW_IN)
self.rdep_treeview = PackageDepView(self.depends_model, TYPE_RDEP, "Runtime Depends")
self.rdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
scrolled.add(self.rdep_treeview)
box.add(scrolled)
# Build Depends
scrolled = gtk.ScrolledWindow()
scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolled.set_shadow_type(gtk.SHADOW_IN)
self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Build Depends")
self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
scrolled.add(self.dep_treeview)
box.add(scrolled)
pane.add2(box)
# Reverse Depends
scrolled = gtk.ScrolledWindow()
scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolled.set_shadow_type(gtk.SHADOW_IN)
self.revdep_treeview = PackageReverseDepView(self.depends_model, "Reverse Depends")
self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT)
scrolled.add(self.revdep_treeview)
box.add(scrolled)
pane.add2(box)
self.show_all()
def on_package_activated(self, treeview, path, column, data_col):
model = treeview.get_model()
package = model.get_value(model.get_iter(path), data_col)
pkg_path = []
def finder(model, path, iter, needle):
package = model.get_value(iter, COL_PKG_NAME)
if package == needle:
pkg_path.append(path)
return True
else:
return False
self.pkg_model.foreach(finder, package)
if pkg_path:
self.pkg_treeview.get_selection().select_path(pkg_path[0])
self.pkg_treeview.scroll_to_cell(pkg_path[0])
def on_cursor_changed(self, selection):
(model, it) = selection.get_selected()
if iter is None:
current_package = None
else:
current_package = model.get_value(it, COL_PKG_NAME)
self.rdep_treeview.set_current_package(current_package)
self.dep_treeview.set_current_package(current_package)
self.revdep_treeview.set_current_package(current_package)
def parse(depgraph, pkg_model, depends_model):
for package in depgraph["pn"]:
pkg_model.set(pkg_model.append(), COL_PKG_NAME, package)
for package in depgraph["depends"]:
for depend in depgraph["depends"][package]:
depends_model.set (depends_model.append(),
COL_DEP_TYPE, TYPE_DEP,
COL_DEP_PARENT, package,
COL_DEP_PACKAGE, depend)
for package in depgraph["rdepends-pn"]:
for rdepend in depgraph["rdepends-pn"][package]:
depends_model.set (depends_model.append(),
COL_DEP_TYPE, TYPE_RDEP,
COL_DEP_PARENT, package,
COL_DEP_PACKAGE, rdepend)
class ProgressBar(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
self.set_title("Parsing .bb files, please wait...")
self.set_default_size(500, 0)
self.connect("delete-event", gtk.main_quit)
self.progress = gtk.ProgressBar()
self.add(self.progress)
self.show_all()
class gtkthread(threading.Thread):
quit = threading.Event()
def __init__(self, shutdown):
threading.Thread.__init__(self)
self.setDaemon(True)
self.shutdown = shutdown
def run(self):
gobject.threads_init()
gtk.gdk.threads_init()
gtk.main()
gtkthread.quit.set()
def init(server, eventHandler):
try:
cmdline = server.runCommand(["getCmdLineAction"])
if not cmdline or cmdline[0] != "generateDotGraph":
print "This UI is only compatible with the -g option"
return
ret = server.runCommand(["generateDepTreeEvent", cmdline[1], cmdline[2]])
if ret != True:
print "Couldn't run command! %s" % ret
return
except xmlrpclib.Fault, x:
print "XMLRPC Fault getting commandline:\n %s" % x
return
shutdown = 0
gtkgui = gtkthread(shutdown)
gtkgui.start()
gtk.gdk.threads_enter()
pbar = ProgressBar()
dep = DepExplorer()
gtk.gdk.threads_leave()
while True:
try:
event = eventHandler.waitEvent(0.25)
if gtkthread.quit.isSet():
break
if event is None:
continue
if isinstance(event, bb.event.ParseProgress):
x = event.sofar
y = event.total
if x == y:
print("\nParsing finished. %d cached, %d parsed, %d skipped, %d masked, %d errors."
% ( event.cached, event.parsed, event.skipped, event.masked, event.errors))
pbar.hide()
gtk.gdk.threads_enter()
pbar.progress.set_fraction(float(x)/float(y))
pbar.progress.set_text("%d/%d (%2d %%)" % (x, y, x*100/y))
gtk.gdk.threads_leave()
continue
if isinstance(event, bb.event.DepTreeGenerated):
gtk.gdk.threads_enter()
parse(event._depgraph, dep.pkg_model, dep.depends_model)
gtk.gdk.threads_leave()
if isinstance(event, bb.command.CookerCommandCompleted):
continue
if isinstance(event, bb.command.CookerCommandFailed):
print "Command execution failed: %s" % event.error
break
if isinstance(event, bb.cooker.CookerExit):
break
continue
except KeyboardInterrupt:
if shutdown == 2:
print "\nThird Keyboard Interrupt, exit.\n"
break
if shutdown == 1:
print "\nSecond Keyboard Interrupt, stopping...\n"
server.runCommand(["stateStop"])
if shutdown == 0:
print "\nKeyboard Interrupt, closing down...\n"
server.runCommand(["stateShutdown"])
shutdown = shutdown + 1
pass
+77
View File
@@ -0,0 +1,77 @@
#
# 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 gobject
import gtk
import xmlrpclib
from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild
def event_handle_idle_func (eventHandler, build):
# Consume as many messages as we can in the time available to us
event = eventHandler.getEvent()
while event:
build.handle_event (event)
event = eventHandler.getEvent()
return True
class MainWindow (gtk.Window):
def __init__ (self):
gtk.Window.__init__ (self, gtk.WINDOW_TOPLEVEL)
# Setup tree view and the scrolled window
scrolled_window = gtk.ScrolledWindow ()
self.add (scrolled_window)
self.cur_build_tv = RunningBuildTreeView()
scrolled_window.add (self.cur_build_tv)
def init (server, eventHandler):
gobject.threads_init()
gtk.gdk.threads_init()
window = MainWindow ()
window.show_all ()
# Create the object for the current build
running_build = RunningBuild ()
window.cur_build_tv.set_model (running_build.model)
try:
cmdline = server.runCommand(["getCmdLineAction"])
print cmdline
if not cmdline:
return 1
ret = server.runCommand(cmdline)
if ret != True:
print "Couldn't get default commandline! %s" % ret
return 1
except xmlrpclib.Fault, x:
print "XMLRPC Fault getting commandline:\n %s" % x
return 1
# Use a timeout function for probing the event queue to find out if we
# have a message waiting for us.
gobject.timeout_add (200,
event_handle_idle_func,
eventHandler,
running_build)
gtk.main()
+162
View File
@@ -0,0 +1,162 @@
#
# BitBake (No)TTY UI Implementation
#
# Handling output to TTYs or files (no TTY)
#
# 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.
import os
import sys
import itertools
import xmlrpclib
parsespin = itertools.cycle( r'|/-\\' )
def init(server, eventHandler):
# Get values of variables which control our output
includelogs = server.runCommand(["getVariable", "BBINCLUDELOGS"])
loglines = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
try:
cmdline = server.runCommand(["getCmdLineAction"])
#print cmdline
if not cmdline:
return 1
ret = server.runCommand(cmdline)
if ret != True:
print "Couldn't get default commandline! %s" % ret
return 1
except xmlrpclib.Fault, x:
print "XMLRPC Fault getting commandline:\n %s" % x
return 1
shutdown = 0
return_value = 0
while True:
try:
event = eventHandler.waitEvent(0.25)
if event is None:
continue
#print event
if isinstance(event, bb.msg.MsgPlain):
print event._message
continue
if isinstance(event, bb.msg.MsgDebug):
print 'DEBUG: ' + event._message
continue
if isinstance(event, bb.msg.MsgNote):
print 'NOTE: ' + event._message
continue
if isinstance(event, bb.msg.MsgWarn):
print 'WARNING: ' + event._message
continue
if isinstance(event, bb.msg.MsgError):
return_value = 1
print 'ERROR: ' + event._message
continue
if isinstance(event, bb.msg.MsgFatal):
return_value = 1
print 'FATAL: ' + event._message
break
if isinstance(event, bb.build.TaskFailed):
return_value = 1
logfile = event.logfile
if logfile:
print "ERROR: Logfile of failure stored in %s." % logfile
if 1 or includelogs:
print "Log data follows:"
f = open(logfile, "r")
lines = []
while True:
l = f.readline()
if l == '':
break
l = l.rstrip()
if loglines:
lines.append(' | %s' % l)
if len(lines) > int(loglines):
lines.pop(0)
else:
print '| %s' % l
f.close()
if lines:
for line in lines:
print line
if isinstance(event, bb.build.TaskBase):
print "NOTE: %s" % event._message
continue
if isinstance(event, bb.event.ParseProgress):
x = event.sofar
y = event.total
if os.isatty(sys.stdout.fileno()):
sys.stdout.write("\rNOTE: Handling BitBake files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
sys.stdout.flush()
else:
if x == 1:
sys.stdout.write("Parsing .bb files, please wait...")
sys.stdout.flush()
if x == y:
sys.stdout.write("done.")
sys.stdout.flush()
if x == y:
print("\nParsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors."
% ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors))
continue
if isinstance(event, bb.command.CookerCommandCompleted):
break
if isinstance(event, bb.command.CookerCommandSetExitCode):
return_value = event.exitcode
continue
if isinstance(event, bb.command.CookerCommandFailed):
return_value = 1
print "Command execution failed: %s" % event.error
break
if isinstance(event, bb.cooker.CookerExit):
break
# ignore
if isinstance(event, bb.event.BuildStarted):
continue
if isinstance(event, bb.event.BuildCompleted):
continue
if isinstance(event, bb.event.MultipleProviders):
continue
if isinstance(event, bb.runqueue.runQueueEvent):
continue
if isinstance(event, bb.event.StampUpdate):
continue
if isinstance(event, bb.event.ConfigParsed):
continue
if isinstance(event, bb.event.RecipeParsed):
continue
print "Unknown Event: %s" % event
except KeyboardInterrupt:
if shutdown == 2:
print "\nThird Keyboard Interrupt, exit.\n"
break
if shutdown == 1:
print "\nSecond Keyboard Interrupt, stopping...\n"
server.runCommand(["stateStop"])
if shutdown == 0:
print "\nKeyboard Interrupt, closing down...\n"
server.runCommand(["stateShutdown"])
shutdown = shutdown + 1
pass
return return_value
+335
View File
@@ -0,0 +1,335 @@
#
# BitBake Curses UI Implementation
#
# Implements an ncurses frontend for the BitBake utility.
#
# Copyright (C) 2006 Michael 'Mickey' Lauer
# 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.
"""
We have the following windows:
1.) Main Window: Shows what we are ultimately building and how far we are. Includes status bar
2.) Thread Activity Window: Shows one status line for every concurrent bitbake thread.
3.) Command Line Window: Contains an interactive command line where you can interact w/ Bitbake.
Basic window layout is like that:
|---------------------------------------------------------|
| <Main Window> | <Thread Activity Window> |
| | 0: foo do_compile complete|
| Building Gtk+-2.6.10 | 1: bar do_patch complete |
| Status: 60% | ... |
| | ... |
| | ... |
|---------------------------------------------------------|
|<Command Line Window> |
|>>> which virtual/kernel |
|openzaurus-kernel |
|>>> _ |
|---------------------------------------------------------|
"""
import os, sys, curses, itertools, time
import bb
import xmlrpclib
from bb import ui
from bb.ui import uihelper
parsespin = itertools.cycle( r'|/-\\' )
X = 0
Y = 1
WIDTH = 2
HEIGHT = 3
MAXSTATUSLENGTH = 32
class NCursesUI:
"""
NCurses UI Class
"""
class Window:
"""Base Window Class"""
def __init__( self, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ):
self.win = curses.newwin( height, width, y, x )
self.dimensions = ( x, y, width, height )
"""
if curses.has_colors():
color = 1
curses.init_pair( color, fg, bg )
self.win.bkgdset( ord(' '), curses.color_pair(color) )
else:
self.win.bkgdset( ord(' '), curses.A_BOLD )
"""
self.erase()
self.setScrolling()
self.win.noutrefresh()
def erase( self ):
self.win.erase()
def setScrolling( self, b = True ):
self.win.scrollok( b )
self.win.idlok( b )
def setBoxed( self ):
self.boxed = True
self.win.box()
self.win.noutrefresh()
def setText( self, x, y, text, *args ):
self.win.addstr( y, x, text, *args )
self.win.noutrefresh()
def appendText( self, text, *args ):
self.win.addstr( text, *args )
self.win.noutrefresh()
def drawHline( self, y ):
self.win.hline( y, 0, curses.ACS_HLINE, self.dimensions[WIDTH] )
self.win.noutrefresh()
class DecoratedWindow( Window ):
"""Base class for windows with a box and a title bar"""
def __init__( self, title, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ):
NCursesUI.Window.__init__( self, x+1, y+3, width-2, height-4, fg, bg )
self.decoration = NCursesUI.Window( x, y, width, height, fg, bg )
self.decoration.setBoxed()
self.decoration.win.hline( 2, 1, curses.ACS_HLINE, width-2 )
self.setTitle( title )
def setTitle( self, title ):
self.decoration.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
#-------------------------------------------------------------------------#
# class TitleWindow( Window ):
#-------------------------------------------------------------------------#
# """Title Window"""
# def __init__( self, x, y, width, height ):
# NCursesUI.Window.__init__( self, x, y, width, height )
# version = bb.__version__
# title = "BitBake %s" % version
# credit = "(C) 2003-2007 Team BitBake"
# #self.win.hline( 2, 1, curses.ACS_HLINE, width-2 )
# self.win.border()
# self.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
# self.setText( 1, 2, credit.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
#-------------------------------------------------------------------------#
class ThreadActivityWindow( DecoratedWindow ):
#-------------------------------------------------------------------------#
"""Thread Activity Window"""
def __init__( self, x, y, width, height ):
NCursesUI.DecoratedWindow.__init__( self, "Thread Activity", x, y, width, height )
def setStatus( self, thread, text ):
line = "%02d: %s" % ( thread, text )
width = self.dimensions[WIDTH]
if ( len(line) > width ):
line = line[:width-3] + "..."
else:
line = line.ljust( width )
self.setText( 0, thread, line )
#-------------------------------------------------------------------------#
class MainWindow( DecoratedWindow ):
#-------------------------------------------------------------------------#
"""Main Window"""
def __init__( self, x, y, width, height ):
self.StatusPosition = width - MAXSTATUSLENGTH
NCursesUI.DecoratedWindow.__init__( self, None, x, y, width, height )
curses.nl()
def setTitle( self, title ):
title = "BitBake %s" % bb.__version__
self.decoration.setText( 2, 1, title, curses.A_BOLD )
self.decoration.setText( self.StatusPosition - 8, 1, "Status:", curses.A_BOLD )
def setStatus(self, status):
while len(status) < MAXSTATUSLENGTH:
status = status + " "
self.decoration.setText( self.StatusPosition, 1, status, curses.A_BOLD )
#-------------------------------------------------------------------------#
class ShellOutputWindow( DecoratedWindow ):
#-------------------------------------------------------------------------#
"""Interactive Command Line Output"""
def __init__( self, x, y, width, height ):
NCursesUI.DecoratedWindow.__init__( self, "Command Line Window", x, y, width, height )
#-------------------------------------------------------------------------#
class ShellInputWindow( Window ):
#-------------------------------------------------------------------------#
"""Interactive Command Line Input"""
def __init__( self, x, y, width, height ):
NCursesUI.Window.__init__( self, x, y, width, height )
# put that to the top again from curses.textpad import Textbox
# self.textbox = Textbox( self.win )
# t = threading.Thread()
# t.run = self.textbox.edit
# t.start()
#-------------------------------------------------------------------------#
def main(self, stdscr, server, eventHandler):
#-------------------------------------------------------------------------#
height, width = stdscr.getmaxyx()
# for now split it like that:
# MAIN_y + THREAD_y = 2/3 screen at the top
# MAIN_x = 2/3 left, THREAD_y = 1/3 right
# CLI_y = 1/3 of screen at the bottom
# CLI_x = full
main_left = 0
main_top = 0
main_height = ( height / 3 * 2 )
main_width = ( width / 3 ) * 2
clo_left = main_left
clo_top = main_top + main_height
clo_height = height - main_height - main_top - 1
clo_width = width
cli_left = main_left
cli_top = clo_top + clo_height
cli_height = 1
cli_width = width
thread_left = main_left + main_width
thread_top = main_top
thread_height = main_height
thread_width = width - main_width
#tw = self.TitleWindow( 0, 0, width, main_top )
mw = self.MainWindow( main_left, main_top, main_width, main_height )
taw = self.ThreadActivityWindow( thread_left, thread_top, thread_width, thread_height )
clo = self.ShellOutputWindow( clo_left, clo_top, clo_width, clo_height )
cli = self.ShellInputWindow( cli_left, cli_top, cli_width, cli_height )
cli.setText( 0, 0, "BB>" )
mw.setStatus("Idle")
helper = uihelper.BBUIHelper()
shutdown = 0
try:
cmdline = server.runCommand(["getCmdLineAction"])
if not cmdline:
return
ret = server.runCommand(cmdline)
if ret != True:
print "Couldn't get default commandlind! %s" % ret
return
except xmlrpclib.Fault, x:
print "XMLRPC Fault getting commandline:\n %s" % x
return
exitflag = False
while not exitflag:
try:
event = eventHandler.waitEvent(0.25)
if not event:
continue
helper.eventHandler(event)
#mw.appendText("%s\n" % event[0])
if isinstance(event, bb.build.Task):
mw.appendText("NOTE: %s\n" % event._message)
if isinstance(event, bb.msg.MsgDebug):
mw.appendText('DEBUG: ' + event._message + '\n')
if isinstance(event, bb.msg.MsgNote):
mw.appendText('NOTE: ' + event._message + '\n')
if isinstance(event, bb.msg.MsgWarn):
mw.appendText('WARNING: ' + event._message + '\n')
if isinstance(event, bb.msg.MsgError):
mw.appendText('ERROR: ' + event._message + '\n')
if isinstance(event, bb.msg.MsgFatal):
mw.appendText('FATAL: ' + event._message + '\n')
if isinstance(event, bb.event.ParseProgress):
x = event.sofar
y = event.total
if x == y:
mw.setStatus("Idle")
mw.appendText("Parsing finished. %d cached, %d parsed, %d skipped, %d masked."
% ( event.cached, event.parsed, event.skipped, event.masked ))
else:
mw.setStatus("Parsing: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
# if isinstance(event, bb.build.TaskFailed):
# if event.logfile:
# if data.getVar("BBINCLUDELOGS", d):
# bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
# number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
# if number_of_lines:
# os.system('tail -n%s %s' % (number_of_lines, logfile))
# else:
# f = open(logfile, "r")
# while True:
# l = f.readline()
# if l == '':
# break
# l = l.rstrip()
# print '| %s' % l
# f.close()
# else:
# bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
if isinstance(event, bb.command.CookerCommandCompleted):
exitflag = True
if isinstance(event, bb.command.CookerCommandFailed):
mw.appendText("Command execution failed: %s" % event.error)
time.sleep(2)
exitflag = True
if isinstance(event, bb.cooker.CookerExit):
exitflag = True
if helper.needUpdate:
activetasks, failedtasks = helper.getTasks()
taw.erase()
taw.setText(0, 0, "")
if activetasks:
taw.appendText("Active Tasks:\n")
for task in activetasks:
taw.appendText(task)
if failedtasks:
taw.appendText("Failed Tasks:\n")
for task in failedtasks:
taw.appendText(task)
curses.doupdate()
except KeyboardInterrupt:
if shutdown == 2:
mw.appendText("Third Keyboard Interrupt, exit.\n")
exitflag = True
if shutdown == 1:
mw.appendText("Second Keyboard Interrupt, stopping...\n")
server.runCommand(["stateStop"])
if shutdown == 0:
mw.appendText("Keyboard Interrupt, closing down...\n")
server.runCommand(["stateShutdown"])
shutdown = shutdown + 1
pass
def init(server, eventHandler):
if not os.isatty(sys.stdout.fileno()):
print "FATAL: Unable to run 'ncurses' UI without a TTY."
return
ui = NCursesUI()
try:
curses.wrapper(ui.main, server, eventHandler)
except:
import traceback
traceback.print_exc()
+425
View File
@@ -0,0 +1,425 @@
#
# 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 gtk.glade
import threading
import urllib2
import os
from bb.ui.crumbs.buildmanager import BuildManager, BuildConfiguration
from bb.ui.crumbs.buildmanager import BuildManagerTreeView
from bb.ui.crumbs.runningbuild import RunningBuild, RunningBuildTreeView
# The metadata loader is used by the BuildSetupDialog to download the
# available options to populate the dialog
class MetaDataLoader(gobject.GObject):
""" This class provides the mechanism for loading the metadata (the
fetching and parsing) from a given URL. The metadata encompasses details
on what machines are available. The distribution and images available for
the machine and the the uris to use for building the given machine."""
__gsignals__ = {
'success' : (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
()),
'error' : (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gobject.TYPE_STRING,))
}
# We use these little helper functions to ensure that we take the gdk lock
# when emitting the signal. These functions are called as idles (so that
# they happen in the gtk / main thread's main loop.
def emit_error_signal (self, remark):
gtk.gdk.threads_enter()
self.emit ("error", remark)
gtk.gdk.threads_leave()
def emit_success_signal (self):
gtk.gdk.threads_enter()
self.emit ("success")
gtk.gdk.threads_leave()
def __init__ (self):
gobject.GObject.__init__ (self)
class LoaderThread(threading.Thread):
""" This class provides an asynchronous loader for the metadata (by
using threads and signals). This is useful since the metadata may be
at a remote URL."""
class LoaderImportException (Exception):
pass
def __init__(self, loader, url):
threading.Thread.__init__ (self)
self.url = url
self.loader = loader
def run (self):
result = {}
try:
f = urllib2.urlopen (self.url)
# Parse the metadata format. The format is....
# <machine>;<default distro>|<distro>...;<default image>|<image>...;<type##url>|...
for line in f.readlines():
components = line.split(";")
if (len (components) < 4):
raise MetaDataLoader.LoaderThread.LoaderImportException
machine = components[0]
distros = components[1].split("|")
images = components[2].split("|")
urls = components[3].split("|")
result[machine] = (distros, images, urls)
# Create an object representing this *potential*
# configuration. It can become concrete if the machine, distro
# and image are all chosen in the UI
configuration = BuildConfiguration()
configuration.metadata_url = self.url
configuration.machine_options = result
self.loader.configuration = configuration
# Emit that we've actually got a configuration
gobject.idle_add (MetaDataLoader.emit_success_signal,
self.loader)
except MetaDataLoader.LoaderThread.LoaderImportException, e:
gobject.idle_add (MetaDataLoader.emit_error_signal, self.loader,
"Repository metadata corrupt")
except Exception, e:
gobject.idle_add (MetaDataLoader.emit_error_signal, self.loader,
"Unable to download repository metadata")
print e
def try_fetch_from_url (self, url):
# Try and download the metadata. Firing a signal if successful
thread = MetaDataLoader.LoaderThread(self, url)
thread.start()
class BuildSetupDialog (gtk.Dialog):
RESPONSE_BUILD = 1
# A little helper method that just sets the states on the widgets based on
# whether we've got good metadata or not.
def set_configurable (self, configurable):
if (self.configurable == configurable):
return
self.configurable = configurable
for widget in self.conf_widgets:
widget.set_sensitive (configurable)
if not configurable:
self.machine_combo.set_active (-1)
self.distribution_combo.set_active (-1)
self.image_combo.set_active (-1)
# GTK widget callbacks
def refresh_button_clicked (self, button):
# Refresh button clicked.
url = self.location_entry.get_chars (0, -1)
self.loader.try_fetch_from_url(url)
def repository_entry_editable_changed (self, entry):
if (len (entry.get_chars (0, -1)) > 0):
self.refresh_button.set_sensitive (True)
else:
self.refresh_button.set_sensitive (False)
self.clear_status_message()
# If we were previously configurable we are no longer since the
# location entry has been changed
self.set_configurable (False)
def machine_combo_changed (self, combobox):
active_iter = combobox.get_active_iter()
if not active_iter:
return
model = combobox.get_model()
if model:
chosen_machine = model.get (active_iter, 0)[0]
(distros_model, images_model) = \
self.loader.configuration.get_distro_and_images_models (chosen_machine)
self.distribution_combo.set_model (distros_model)
self.image_combo.set_model (images_model)
# Callbacks from the loader
def loader_success_cb (self, loader):
self.status_image.set_from_icon_name ("info",
gtk.ICON_SIZE_BUTTON)
self.status_image.show()
self.status_label.set_label ("Repository metadata successfully downloaded")
# Set the models on the combo boxes based on the models generated from
# the configuration that the loader has created
# We just need to set the machine here, that then determines the
# distro and image options. Cunning huh? :-)
self.configuration = self.loader.configuration
model = self.configuration.get_machines_model ()
self.machine_combo.set_model (model)
self.set_configurable (True)
def loader_error_cb (self, loader, message):
self.status_image.set_from_icon_name ("error",
gtk.ICON_SIZE_BUTTON)
self.status_image.show()
self.status_label.set_text ("Error downloading repository metadata")
for widget in self.conf_widgets:
widget.set_sensitive (False)
def clear_status_message (self):
self.status_image.hide()
self.status_label.set_label (
"""<i>Enter the repository location and press _Refresh</i>""")
def __init__ (self):
gtk.Dialog.__init__ (self)
# Cancel
self.add_button (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
# Build
button = gtk.Button ("_Build", None, True)
image = gtk.Image ()
image.set_from_stock (gtk.STOCK_EXECUTE,gtk.ICON_SIZE_BUTTON)
button.set_image (image)
self.add_action_widget (button, BuildSetupDialog.RESPONSE_BUILD)
button.show_all ()
# Pull in *just* the table from the Glade XML data.
gxml = gtk.glade.XML (os.path.dirname(__file__) + "/crumbs/puccho.glade",
root = "build_table")
table = gxml.get_widget ("build_table")
self.vbox.pack_start (table, True, False, 0)
# Grab all the widgets that we need to turn on/off when we refresh...
self.conf_widgets = []
self.conf_widgets += [gxml.get_widget ("machine_label")]
self.conf_widgets += [gxml.get_widget ("distribution_label")]
self.conf_widgets += [gxml.get_widget ("image_label")]
self.conf_widgets += [gxml.get_widget ("machine_combo")]
self.conf_widgets += [gxml.get_widget ("distribution_combo")]
self.conf_widgets += [gxml.get_widget ("image_combo")]
# Grab the status widgets
self.status_image = gxml.get_widget ("status_image")
self.status_label = gxml.get_widget ("status_label")
# Grab the refresh button and connect to the clicked signal
self.refresh_button = gxml.get_widget ("refresh_button")
self.refresh_button.connect ("clicked", self.refresh_button_clicked)
# Grab the location entry and connect to editable::changed
self.location_entry = gxml.get_widget ("location_entry")
self.location_entry.connect ("changed",
self.repository_entry_editable_changed)
# Grab the machine combo and hook onto the changed signal. This then
# allows us to populate the distro and image combos
self.machine_combo = gxml.get_widget ("machine_combo")
self.machine_combo.connect ("changed", self.machine_combo_changed)
# Setup the combo
cell = gtk.CellRendererText()
self.machine_combo.pack_start(cell, True)
self.machine_combo.add_attribute(cell, 'text', 0)
# Grab the distro and image combos. We need these to populate with
# models once the machine is chosen
self.distribution_combo = gxml.get_widget ("distribution_combo")
cell = gtk.CellRendererText()
self.distribution_combo.pack_start(cell, True)
self.distribution_combo.add_attribute(cell, 'text', 0)
self.image_combo = gxml.get_widget ("image_combo")
cell = gtk.CellRendererText()
self.image_combo.pack_start(cell, True)
self.image_combo.add_attribute(cell, 'text', 0)
# Put the default descriptive text in the status box
self.clear_status_message()
# Mark as non-configurable, this is just greys out the widgets the
# user can't yet use
self.configurable = False
self.set_configurable(False)
# Show the table
table.show_all ()
# The loader and some signals connected to it to update the status
# area
self.loader = MetaDataLoader()
self.loader.connect ("success", self.loader_success_cb)
self.loader.connect ("error", self.loader_error_cb)
def update_configuration (self):
""" A poorly named function but it updates the internal configuration
from the widgets. This can make that configuration concrete and can
thus be used for building """
# Extract the chosen machine from the combo
model = self.machine_combo.get_model()
active_iter = self.machine_combo.get_active_iter()
if (active_iter):
self.configuration.machine = model.get(active_iter, 0)[0]
# Extract the chosen distro from the combo
model = self.distribution_combo.get_model()
active_iter = self.distribution_combo.get_active_iter()
if (active_iter):
self.configuration.distro = model.get(active_iter, 0)[0]
# Extract the chosen image from the combo
model = self.image_combo.get_model()
active_iter = self.image_combo.get_active_iter()
if (active_iter):
self.configuration.image = model.get(active_iter, 0)[0]
# This function operates to pull events out from the event queue and then push
# them into the RunningBuild (which then drives the RunningBuild which then
# pushes through and updates the progress tree view.)
#
# TODO: Should be a method on the RunningBuild class
def event_handle_timeout (eventHandler, build):
# Consume as many messages as we can ...
event = eventHandler.getEvent()
while event:
build.handle_event (event)
event = eventHandler.getEvent()
return True
class MainWindow (gtk.Window):
# Callback that gets fired when the user hits a button in the
# BuildSetupDialog.
def build_dialog_box_response_cb (self, dialog, response_id):
conf = None
if (response_id == BuildSetupDialog.RESPONSE_BUILD):
dialog.update_configuration()
print dialog.configuration.machine, dialog.configuration.distro, \
dialog.configuration.image
conf = dialog.configuration
dialog.destroy()
if conf:
self.manager.do_build (conf)
def build_button_clicked_cb (self, button):
dialog = BuildSetupDialog ()
# For some unknown reason Dialog.run causes nice little deadlocks ... :-(
dialog.connect ("response", self.build_dialog_box_response_cb)
dialog.show()
def __init__ (self):
gtk.Window.__init__ (self)
# Pull in *just* the main vbox from the Glade XML data and then pack
# that inside the window
gxml = gtk.glade.XML (os.path.dirname(__file__) + "/crumbs/puccho.glade",
root = "main_window_vbox")
vbox = gxml.get_widget ("main_window_vbox")
self.add (vbox)
# Create the tree views for the build manager view and the progress view
self.build_manager_view = BuildManagerTreeView()
self.running_build_view = RunningBuildTreeView()
# Grab the scrolled windows that we put the tree views into
self.results_scrolledwindow = gxml.get_widget ("results_scrolledwindow")
self.progress_scrolledwindow = gxml.get_widget ("progress_scrolledwindow")
# Put the tree views inside ...
self.results_scrolledwindow.add (self.build_manager_view)
self.progress_scrolledwindow.add (self.running_build_view)
# Hook up the build button...
self.build_button = gxml.get_widget ("main_toolbutton_build")
self.build_button.connect ("clicked", self.build_button_clicked_cb)
# I'm not very happy about the current ownership of the RunningBuild. I have
# my suspicions that this object should be held by the BuildManager since we
# care about the signals in the manager
def running_build_succeeded_cb (running_build, manager):
# Notify the manager that a build has succeeded. This is necessary as part
# of the 'hack' that we use for making the row in the model / view
# representing the ongoing build change into a row representing the
# completed build. Since we know only one build can be running a time then
# we can handle this.
# FIXME: Refactor all this so that the RunningBuild is owned by the
# BuildManager. It can then hook onto the signals directly and drive
# interesting things it cares about.
manager.notify_build_succeeded ()
print "build succeeded"
def running_build_failed_cb (running_build, manager):
# As above
print "build failed"
manager.notify_build_failed ()
def init (server, eventHandler):
# Initialise threading...
gobject.threads_init()
gtk.gdk.threads_init()
main_window = MainWindow ()
main_window.show_all ()
# Set up the build manager stuff in general
builds_dir = os.path.join (os.getcwd(), "results")
manager = BuildManager (server, builds_dir)
main_window.build_manager_view.set_model (manager.model)
# Do the running build setup
running_build = RunningBuild ()
main_window.running_build_view.set_model (running_build.model)
running_build.connect ("build-succeeded", running_build_succeeded_cb,
manager)
running_build.connect ("build-failed", running_build_failed_cb, manager)
# We need to save the manager into the MainWindow so that the toolbar
# button can use it.
# FIXME: Refactor ?
main_window.manager = manager
# Use a timeout function for probing the event queue to find out if we
# have a message waiting for us.
gobject.timeout_add (200,
event_handle_timeout,
eventHandler,
running_build)
gtk.main()
+125
View File
@@ -0,0 +1,125 @@
# ex:ts=4:sw=4:sts=4:et
# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
#
# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
# 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.
"""
Use this class to fork off a thread to recieve event callbacks from the bitbake
server and queue them for the UI to process. This process must be used to avoid
client/server deadlocks.
"""
import socket, threading, pickle
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
class BBUIEventQueue:
def __init__(self, BBServer):
self.eventQueue = []
self.eventQueueLock = threading.Lock()
self.eventQueueNotify = threading.Event()
self.BBServer = BBServer
self.t = threading.Thread()
self.t.setDaemon(True)
self.t.run = self.startCallbackHandler
self.t.start()
def getEvent(self):
self.eventQueueLock.acquire()
if len(self.eventQueue) == 0:
self.eventQueueLock.release()
return None
item = self.eventQueue.pop(0)
if len(self.eventQueue) == 0:
self.eventQueueNotify.clear()
self.eventQueueLock.release()
return item
def waitEvent(self, delay):
self.eventQueueNotify.wait(delay)
return self.getEvent()
def queue_event(self, event):
self.eventQueueLock.acquire()
self.eventQueue.append(pickle.loads(event))
self.eventQueueNotify.set()
self.eventQueueLock.release()
def startCallbackHandler(self):
server = UIXMLRPCServer()
self.host, self.port = server.socket.getsockname()
server.register_function( self.system_quit, "event.quit" )
server.register_function( self.queue_event, "event.send" )
server.socket.settimeout(1)
self.EventHandle = self.BBServer.registerEventHandler(self.host, self.port)
self.server = server
while not server.quit:
server.handle_request()
server.server_close()
def system_quit( self ):
"""
Shut down the callback thread
"""
try:
self.BBServer.unregisterEventHandler(self.EventHandle)
except:
pass
self.server.quit = True
class UIXMLRPCServer (SimpleXMLRPCServer):
def __init__( self, interface = ("localhost", 0) ):
self.quit = False
SimpleXMLRPCServer.__init__( self,
interface,
requestHandler=SimpleXMLRPCRequestHandler,
logRequests=False, allow_none=True)
def get_request(self):
while not self.quit:
try:
sock, addr = self.socket.accept()
sock.settimeout(1)
return (sock, addr)
except socket.timeout:
pass
return (None,None)
def close_request(self, request):
if request is None:
return
SimpleXMLRPCServer.close_request(self, request)
def process_request(self, request, client_address):
if request is None:
return
SimpleXMLRPCServer.process_request(self, request, client_address)
+49
View File
@@ -0,0 +1,49 @@
# ex:ts=4:sw=4:sts=4:et
# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
#
# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
# 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.
class BBUIHelper:
def __init__(self):
self.needUpdate = False
self.running_tasks = {}
self.failed_tasks = {}
def eventHandler(self, event):
if isinstance(event, bb.build.TaskStarted):
self.running_tasks["%s %s\n" % (event._package, event._task)] = ""
self.needUpdate = True
if isinstance(event, bb.build.TaskSucceeded):
del self.running_tasks["%s %s\n" % (event._package, event._task)]
self.needUpdate = True
if isinstance(event, bb.build.TaskFailed):
del self.running_tasks["%s %s\n" % (event._package, event._task)]
self.failed_tasks["%s %s\n" % (event._package, event._task)] = ""
self.needUpdate = True
# Add runqueue event handling
#if isinstance(event, bb.runqueue.runQueueTaskCompleted):
# a = 1
#if isinstance(event, bb.runqueue.runQueueTaskStarted):
# a = 1
#if isinstance(event, bb.runqueue.runQueueTaskFailed):
# a = 1
#if isinstance(event, bb.runqueue.runQueueExitWait):
# a = 1
def getTasks(self):
return (self.running_tasks, self.failed_tasks)