Compare commits

...

21 Commits

Author SHA1 Message Date
Alexandru DAMIAN
1182665de0 bitbake: cooker, toaster: variable definition tracking
In order to track the file where a configuration
variable was defined, this patch bring these changes:

* a new feature is defined in CookerFeatures, named
BASEDATASTORE_TRACKING. When a UI requests BASEDATASTORE_TRACKING,
the base variable definition are tracked when configuration
is parsed.

* getAllKeysWithFlags now includes variable history in the
data dump

* toaster_ui.py will record the operation, file path
and line number where the variable was changes

* toaster Simple UI will display the file path
and line number for Configuration page

There is a change in the models to accomodate the recording
of variable change history.

[YOCTO #5227]

(Bitbake rev: 78e58fed82f2a71f052485de0052d7b9cca53ffd)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-15 12:18:03 +00:00
Cristiana Voicu
6302d1baf5 bitbake: toaster: task with outcome 2 (sstate), should have sstate_result!=0
0 (not applicable) is not a valid sstate_result for tasks with
outcome 2 (sstate), which should return 3 (restored), 2
(failed) or 1 (missed).
Sstate_result for tasks with outcome 2 is equal to the outcome
of _setscene corespondent task.

[YOCTO #5220]
(Bitbake rev: 8ff8d75318ea88ba80c744b471e486901ef6749a)

Signed-off-by: Cristiana Voicu <cristiana.voicu@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-15 12:17:50 +00:00
Alexandru DAMIAN
3390674247 bitbake: toaster: fix path to buildstats file
The buildstats file path changes based on the
optional PE variable that may be defined for a
recipe.

The toasterui simply ignored the PE value, and
as such it didn't correctly reach buildstats files
for some of the tasks.

This patch fixes the issue.

    [YOCTO #5073]

(Bitbake rev: 97b8ab88edc7c8dfb26b4cf305701ec96e52cc4f)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-15 12:17:29 +00:00
Alexandru DAMIAN
0e6bbf8181 bitbake: toasterui: mark failed sceneQueue tasks as failed
This patch addresses an issue where a failed sceneQueue task
entry was not updated on the Fail event. As a result, it
always showed the task as not-available.

    [YOCTO #5216]

(Bitbake rev: 9b99a417f58381bac4bda412bcfd11de50403318)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-15 12:17:19 +00:00
Alexandru DAMIAN
3b113312fd bitbake: toaster: remove author field
The AUTHOR field in most recipes is not defined,
or it's not really consistently set in the metadata,
Also does it seem particularly useful.

This patch removes the AUTHOR variable from the
toaster system

    [YOCTO #5449]

(Bitbake rev: da3ac049300be84defab7b32b0b99ab07c7d0a27)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-15 12:17:09 +00:00
Alexandru DAMIAN
6f9c4d9778 bitbake: toaster: fix tasks showing as NoExec
Tasks without script type information showed by default
as NoExec; this happens for all Prebuild or Covered
tasks, as script type information comes only on TaskStarted
event. Such a default value may drive confusion, as NoExec value
should be reserved for the NoExec-flagged tasks.

This patch adds a new default value named Unknown that will be
used for all tasks that don't have script type information
available.

    [YOCTO #5327]

(Bitbake rev: ec6cac74290f0d4f5b60222019c23416b4b8e1ef)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-15 12:16:58 +00:00
Cristiana Voicu
f20de8ac90 bitbake: toaster: convert build_package size to bytes to keep consistence
[YOCTO #5503]
(Bitbake rev: 19eb6e01b675c439ff0a817be6fa5e34ad42ba37)

Signed-off-by: Cristiana Voicu <cristiana.voicu@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-15 12:16:38 +00:00
Alexandru DAMIAN
e5cf3e598e bitbake: toaster: fix target package information
Toaster needs to record information about packages
installed on a built target image, and dependencies
between these packages.

This patch fixes a bug where the variable from the
server wasn't read correctly leading which caused
the buildhistory to not be processed correctly.

Additionally, two display issues in the package table
were fixed, issues that lead to package information
being displayed incorrectly.

    [YOCTO #5197]

(Bitbake rev: ab4bc18409d80de6d069e3dd76c3c54964fe5764)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-05 12:50:58 +00:00
Cristiana Voicu
46814c99ee bitbake: toaster: add variable description for prefixed/suffixed variables
In the Configuration table, we need to link prefixed / suffixed
variables to the corresponding variable descriptions in documentation.conf.

[YOCTO #5198]

(Bitbake rev: 641d9c4fda5fe978154fdfab978c3c09e3906eab)

Signed-off-by: Cristiana Voicu <cristiana.voicu@intel.com>
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-04 10:56:04 +00:00
Alexandru DAMIAN
b0c24033bb bitbake: toaster: enable required classes in the toaster startup script
In order to use toaster, now you have to set INHERIT+="toaster buildhistory"
To keep it simple, I've done some changes in order to automate it. When toaster
is started, this line is added to a new file called toaster.conf.

This file is passed to the bitbake server with the --postread parameter.

Based on a patch by Cristiana Voicu <cristiana.voicu@intel.com>

(Bitbake rev: 029e868044989eda370340f8bf4200cfd2670fca)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-04 10:55:54 +00:00
Alexandru DAMIAN
25f50e24c7 bitbake: toaster: server shutdown on terminal exit
If the terminal where the server was started is closed,
the bitbake server should shutdown. Currently the system
is left in hanging state.

This patch uses "trap" command to make sure the servers
are closed on terminal exit.

[YOCTO #5376]

(Bitbake rev: 5f8b97010f7b465753b6ff6275d18426006ee14b)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-04 10:53:50 +00:00
Alexandru DAMIAN
a6b357a9af bitbake: toaster: fix timezone settings
This patch fixes an issue where, if not defined,
the timezone defaults to 'America/Chicago'.

The solution is to set the timezone to current computer's
timezone.

[YOCTO #5186]

(Bitbake rev: a4102b549f04a9b52cdcd318bf511a18ab48067d)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-04 10:53:43 +00:00
Alexandru DAMIAN
57beaf994f bitbake: cooker: do not recreate recipecache in buildfile mode
When building a single file, the cooker will recreate
the recipecache from scratch.

I suspect this is a remnant of past code, since:
* the current recipecache works fine
* the new recipecache will not have all the fields as
requested by HOB_EXTRA_CACHES setting

This patch disables recreating the recipecache, leading
to shorter times when building single build files
(-b option) and better compatibility with Toaster.

(Bitbake rev: 618d69b00075981b8553513130d7deb1aed61578)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-04 10:53:35 +00:00
Alexandru DAMIAN
e1aebfe018 bitbake: build, toaster: record proper task type
Bitbake tasks may be of type 'python' or 'shell',
or they may not be executed at all, which is record
as task type 'noexec'.

In order to record proper task type, this patch:

* creates no exec task type as the default value in
the toaster model definition

* adds full task flags to the bb.build.TaskStarted event
in build.py

* if the task actually starts, the toaster ui will
record the type of the task as either 'python' or 'shell'
based on the task flags.

[YOCTO #5073]
[YOCTO #5075]
[YOCTO #5327]

(Bitbake rev: 6648c57e6d369fc009ea3a9fe939def5d2c67bf5)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-04 10:53:24 +00:00
Alexandru DAMIAN
f6847b0cd2 bitbake: cooker: add data to the dependency tree dump
Toaster needes to record extra data that needs to
be moved at the time of the dependency tree dump.

This data includes:
* layer priorities for recording in the layer section
* the inherit list for each PN which allows to determine
the type of the PN (regular package, image, etc).

This patch adds this data to the dependency tree dump.

(Bitbake rev: 7636aba37320aaf9b044d3832ddc21af51ccd69c)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-04 10:53:12 +00:00
Alexandru DAMIAN
1e6e27d98d bitbake: toaster: fixes for null values from events
Some of the data values may come of as None through the event system,
and the UI would encounter a problem saving the Configuration.
It would be trying to save these values as NULL in the
database, which is not allowed.

This patch adds more verification for data coming through
the event system.

Other minor updates:
* update for the event model from toaster.bbclass
* minor code flow fix in the event system

(Bitbake rev: 03fafd086381723c6486522873671515824e49f2)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-04 10:52:59 +00:00
Alexandru DAMIAN
73293c6481 toaster: add class to dump toaster-tracked data
Adding a new bbclass that will collect and send relevant
data from the task context to the Toaster UI.

This bbclass consists of postfuncs that get executed
right after the main task func, and in the same context.
This allows data gathering in a synchronous manner during
the build, guaranteeing data integrity. This approach also
preserves the task signatures.

The data is moved to the UI through the event system.

There is no performance impact if the class is disabled.

License is MIT.

(From OE-Core rev: 1d2d37d579492b63d20ff8aa890a43b9a1576cf0)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-04 10:52:45 +00:00
Belen Barros Pena
d7b8f82a64 documentation.conf: update contents
This patch updates descriptions and brings more
info for the variable configurations as documented
in the OE-Core manual.

This file is used by Toaster to display help for
the configuration variables.

(From OE-Core rev: 98405beddb93490c8a2e9903adc2a510969ed6a9)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2013-11-04 10:52:39 +00:00
Alexandru DAMIAN
4b64eb444a bitbake: add Toaster UI interface
Adding a new bitbake UI interface named 'toasterui'.

'toasterui' listens for events and data coming from a
bitbake server during a run, and records it
in a data store using the Toaster object model.

Adds a helper class named BuildInfoHelper that
reconstructs the state of the bitbake server and
saves relevant data to the data store.

Code portions contributed by Calin Dragomir <calindragomir@gmail.com>.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
2013-10-16 15:27:03 +01:00
Alexandru DAMIAN
db2a7845a9 bitbake: toaster: adding frameworks for the Simple UI
This commit adds the 3rd party frameworks used for the web UI.

jQuery is licensed under MIT.

Bootstrap is licensed under APACHE-2.0

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
2013-10-16 15:26:52 +01:00
Alexandru DAMIAN
cb1338dedc bitbake: add toaster code to bitbake
This patch adds the Toaster component to Bitbake.

Toaster is a module designed to record the progress of a
Bitbake build, and data about the resultant artifacts.

It contains a web-based interface and a REST API allowing
post-facto inspection of the build process and artifacts.

Features present in this build:
* toaster start script
* relational data model
* Django boilerplate code
* the REST API
* the Simple UI web interface

This patch has all the development history squashed together.

Code portions contributed by Calin Dragomir <calindragomir@gmail.com>.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
2013-10-16 15:26:37 +01:00
41 changed files with 9485 additions and 14 deletions

10
bitbake/LICENSE Normal file
View File

@@ -0,0 +1,10 @@
BitBake is licensed under the GNU General Public License version 2.0. See COPYING for further details.
The following external components are distributed with this software:
* The Toaster Simple UI application is based upon the Django project template, the files of which are covered by the BSD license and are copyright (c) Django Software
Foundation and individual contributors.
* Twitter Bootstrap (including Glyphicons), redistributed under the Apache License 2.0.
* jQuery is redistributed under the MIT license.

164
bitbake/bin/toaster Executable file
View File

@@ -0,0 +1,164 @@
#!/bin/bash
# (c) 2013 Intel Corp.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This script enables toaster event logging and
# starts bitbake resident server
# use as: source toaster [start|stop]
# Helper function to kill a background toaster development server
function webserverKillAll()
{
local pidfile
for pidfile in ${BUILDDIR}/.toastermain.pid; do
if [ -f ${pidfile} ]; then
while kill -0 $(< ${pidfile}) 2>/dev/null; do
kill -SIGTERM -$(< ${pidfile}) 2>/dev/null
sleep 1;
done;
rm ${pidfile}
fi
done
}
function webserverStartAll()
{
retval=0
python $BBBASEDIR/lib/toaster/manage.py syncdb || retval=1
if [ $retval -eq 1 ]; then
echo "Failed db sync, stopping system start" 1>&2
else
python $BBBASEDIR/lib/toaster/manage.py runserver 0.0.0.0:8000 </dev/null >${BUILDDIR}/toaster_web.log 2>&1 & echo $! >${BUILDDIR}/.toastermain.pid
fi
return $retval
}
# Helper functions to add a special configuration file
function addtoConfiguration()
{
echo "#Created by toaster start script" > ${BUILDDIR}/conf/$2
echo $1 >> ${BUILDDIR}/conf/$2
}
# define the stop command
function stop_system()
{
if [ -f ${BUILDDIR}/.toasterui.pid ]; then
kill $(< ${BUILDDIR}/.toasterui.pid )
rm ${BUILDDIR}/.toasterui.pid
fi
BBSERVER=localhost:8200 bitbake -m
unset BBSERVER
webserverKillAll
# force stop any misbehaving bitbake server
lsof bitbake.lock | awk '{print $2}' | grep "[0-9]\+" | xargs -n1 -r kill
}
# We make sure we're running in the current shell and in a good environment
if [ -z "$ZSH_NAME" ] && [ `basename \"$0\"` = `basename \"$BASH_SOURCE\"` ]; then
echo "Error: This script needs to be sourced. Please run as 'source toaster [start|stop]'" 1>&2;
exit 1
fi
if [ -z "$BUILDDIR" ] || [ -z `which bitbake` ]; then
echo "Error: Build environment is not setup or bitbake is not in path." 1>&2;
return 2
fi
BBBASEDIR=`dirname ${BASH_SOURCE}`/..
# Verify prerequisites
if ! echo "import django; print (1,4,5) == django.VERSION[0:3]" | python 2>/dev/null | grep True >/dev/null; then
echo -e "This program needs Django 1.4.5. Please install with\n\nsudo pip install django==1.4.5"
return 2
fi
# Determine the action. If specified by arguments, fine, if not, toggle it
if [ "x$1" == "xstart" ] || [ "x$1" == "xstop" ]; then
CMD="$1"
else
if [ -z "$BBSERVER" ]; then
CMD="start"
else
CMD="stop"
fi;
fi
NOTOASTERUI=0
if [ "x$2" == "xnoui" ]; then
NOTOASTERUI=1
fi
echo "The system will $CMD."
# Make sure it's safe to run by checking bitbake lock
lock=1
if [ -e $BUILDDIR/bitbake.lock ]; then
(flock -n 200 ) 200<$BUILDDIR/bitbake.lock || lock=0
fi
if [ ${CMD} == "start" ] && ( [ $lock -eq 0 ] || [ -e $BUILDDIR/.toastermain.pid ] ); then
echo "Error: bitbake lock state error. System is already on." 2>&1
return 3
elif [ ${CMD} == "stop" ] && ( [ $lock -eq 1 ] || ! [ -e $BUILDDIR/.toastermain.pid ] ) ; then
echo "Error: bitbake lock state error. Trying to stop a stopped system ?
If you think the system is hanged up, you can try to manually stop system with the commands
# BBSERVER=localhost:8200 bitbake -m
and
# webserverKillAll
" 2>&1
return 3
fi
# Execute the commands
case $CMD in
start )
addtoConfiguration "INHERIT+=\"toaster buildhistory\"" toaster.conf
webserverStartAll || return 4
unset BBSERVER
bitbake --postread conf/toaster.conf --server-only -t xmlrpc -B localhost:8200
export BBSERVER=localhost:8200
if [ $NOTOASTERUI == 0 ]; then # we start the TOASTERUI only if not inhibited
bitbake --observe-only -u toasterui >${BUILDDIR}/toaster_ui.log 2>&1 & echo $! >${BUILDDIR}/.toasterui.pid
fi
# stop system on terminal exit
trap stop_system SIGHUP
;;
stop )
stop_system
trap '' SIGHUP
;;
esac
echo "Successful ${CMD}."

View File

@@ -91,6 +91,9 @@ class TaskBase(event.Event):
class TaskStarted(TaskBase):
"""Task execution started"""
def __init__(self, t, logfile, taskflags, d):
super(TaskStarted, self).__init__(t, logfile, d)
self.taskflags = taskflags
class TaskSucceeded(TaskBase):
"""Task execution completed"""
@@ -422,7 +425,9 @@ def _exec_task(fn, task, d, quieterr):
localdata.setVar('BB_LOGFILE', logfn)
localdata.setVar('BB_RUNTASK', task)
event.fire(TaskStarted(task, logfn, localdata), localdata)
flags = localdata.getVarFlags(task)
event.fire(TaskStarted(task, logfn, flags, localdata), localdata)
try:
for func in (prefuncs or '').split():
exec_func(func, localdata)

View File

@@ -81,7 +81,7 @@ class SkippedPackage:
class CookerFeatures(object):
_feature_list = [HOB_EXTRA_CACHES, SEND_DEPENDS_TREE] = range(2)
_feature_list = [HOB_EXTRA_CACHES, SEND_DEPENDS_TREE, BASEDATASTORE_TRACKING] = range(3)
def __init__(self):
self._features=set()
@@ -177,6 +177,9 @@ class BBCooker:
self.data.disableTracking()
def loadConfigurationData(self):
if CookerFeatures.BASEDATASTORE_TRACKING in self.featureset:
self.enableDataTracking()
self.initConfigurationData()
self.databuilder.parseBaseConfiguration()
self.data = self.databuilder.data
@@ -189,6 +192,10 @@ class BBCooker:
bb.data.update_data(self.event_data)
bb.parse.init_parser(self.event_data)
if CookerFeatures.BASEDATASTORE_TRACKING in self.featureset:
self.disableDataTracking()
def modifyConfigurationVar(self, var, val, default_file, op):
if op == "append":
self.appendConfigurationVar(var, val, default_file)
@@ -348,7 +355,6 @@ class BBCooker:
open(confpath, 'w').close()
def parseConfiguration(self):
# Set log file verbosity
verboselogs = bb.utils.to_boolean(self.data.getVar("BB_VERBOSE_LOGS", "0"))
if verboselogs:
@@ -511,6 +517,7 @@ class BBCooker:
depend_tree["packages"] = {}
depend_tree["rdepends-pkg"] = {}
depend_tree["rrecs-pkg"] = {}
depend_tree["layer-priorities"] = self.recipecache.bbfile_config_priorities
for task in xrange(len(rq.rqdata.runq_fnid)):
taskname = rq.rqdata.runq_task[task]
@@ -522,6 +529,7 @@ class BBCooker:
depend_tree["pn"][pn] = {}
depend_tree["pn"][pn]["filename"] = fn
depend_tree["pn"][pn]["version"] = version
depend_tree["pn"][pn]["inherits"] = self.recipecache.inherits.get(fn, None)
# if we have extra caches, list all attributes they bring in
extra_info = []
@@ -1083,7 +1091,6 @@ class BBCooker:
self.buildSetVars()
self.recipecache = bb.cache.CacheData(self.caches_array)
infos = bb.cache.Cache.parse(fn, self.collection.get_file_appends(fn), \
self.data,
self.caches_array)
@@ -1202,7 +1209,10 @@ class BBCooker:
try:
v = self.data.getVar(k, True)
if not k.startswith("__") and not isinstance(v, bb.data_smart.DataSmart):
dump[k] = { 'v' : v }
dump[k] = {
'v' : v ,
'history' : self.data.varhistory.variable(k),
}
for d in flaglist:
dump[k][d] = self.data.getVarFlag(k, d)
except Exception as e:

View File

@@ -0,0 +1,738 @@
#
# BitBake ToasterUI Implementation
#
# Copyright (C) 2013 Intel Corporation
#
# 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 datetime
import sys
import bb
import re
import subprocess
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "toaster.toastermain.settings")
import toaster.toastermain.settings as toaster_django_settings
from toaster.orm.models import Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage
from toaster.orm.models import Variable, VariableHistory
from toaster.orm.models import Target_Package, Build_Package, Build_File
from toaster.orm.models import Task_Dependency, Build_Package_Dependency
from toaster.orm.models import Target_Package_Dependency, Recipe_Dependency
from bb.msg import BBLogFormatter as format
class ORMWrapper(object):
""" This class creates the dictionaries needed to store information in the database
following the format defined by the Django models. It is also used to save this
information in the database.
"""
def __init__(self):
pass
def create_build_object(self, build_info):
build = Build.objects.create(
machine=build_info['machine'],
image_fstypes=build_info['image_fstypes'],
distro=build_info['distro'],
distro_version=build_info['distro_version'],
started_on=build_info['started_on'],
completed_on=build_info['completed_on'],
cooker_log_path=build_info['cooker_log_path'],
build_name=build_info['build_name'],
bitbake_version=build_info['bitbake_version'])
return build
def create_target_objects(self, target_info):
targets = []
for tgt_name in target_info['targets']:
tgt_object = Target.objects.create( build = target_info['build'],
target = tgt_name,
is_image = False,
file_name = "",
file_size = 0);
targets.append(tgt_object)
return targets
def update_build_object(self, build, errors, warnings, taskfailures):
outcome = Build.SUCCEEDED
if errors or taskfailures:
outcome = Build.FAILED
build.completed_on = datetime.datetime.now()
build.errors_no = errors
build.warnings_no = warnings
build.outcome = outcome
build.save()
def get_update_task_object(self, task_information):
task_object, created = Task.objects.get_or_create(
build=task_information['build'],
recipe=task_information['recipe'],
task_name=task_information['task_name'],
)
for v in vars(task_object):
if v in task_information.keys():
vars(task_object)[v] = task_information[v]
# if we got covered by a setscene task, we're SSTATE
if task_object.outcome == Task.OUTCOME_COVERED and 1 == Task.objects.filter(task_executed=True, build = task_object.build, recipe = task_object.recipe, task_name=task_object.task_name+"_setscene").count():
task_object.outcome = Task.OUTCOME_SSTATE
outcome_task_setscene = Task.objects.get(task_executed=True, build = task_object.build,
recipe = task_object.recipe, task_name=task_object.task_name+"_setscene").outcome
if outcome_task_setscene == Task.OUTCOME_SUCCESS:
task_object.sstate_result = Task.SSTATE_RESTORED
elif outcome_task_setscene == Task.OUTCOME_FAILED:
task_object.sstate_result = Task.SSTATE_FAILED
# mark down duration if we have a start time
if 'start_time' in task_information.keys():
duration = datetime.datetime.now() - task_information['start_time']
task_object.elapsed_time = duration.total_seconds()
task_object.save()
return task_object
def get_update_recipe_object(self, recipe_information):
recipe_object, created = Recipe.objects.get_or_create(
layer_version=recipe_information['layer_version'],
file_path=recipe_information['file_path'])
for v in vars(recipe_object):
if v in recipe_information.keys():
vars(recipe_object)[v] = recipe_information[v]
recipe_object.save()
return recipe_object
def get_layer_version_object(self, layer_version_information):
layer_version_object = Layer_Version.objects.get_or_create(
layer = layer_version_information['layer'],
branch = layer_version_information['branch'],
commit = layer_version_information['commit'],
priority = layer_version_information['priority']
)
layer_version_object[0].save()
return layer_version_object[0]
def get_update_layer_object(self, layer_information):
layer_object = Layer.objects.get_or_create(
name=layer_information['name'],
local_path=layer_information['local_path'],
layer_index_url=layer_information['layer_index_url'])
layer_object[0].save()
return layer_object[0]
def save_target_package_information(self, target_obj, packagedict, bldpkgs, recipes):
for p in packagedict:
packagedict[p]['object'] = Target_Package.objects.create( target = target_obj,
name = p,
size = packagedict[p]['size'])
if p in bldpkgs:
packagedict[p]['object'].version = bldpkgs[p]['version']
packagedict[p]['object'].recipe = recipes[bldpkgs[p]['pn']]
packagedict[p]['object'].save()
for p in packagedict:
for (px,deptype) in packagedict[p]['depends']:
Target_Package_Dependency.objects.create( package = packagedict[p]['object'],
depends_on = packagedict[px]['object'],
dep_type = deptype);
def create_logmessage(self, log_information):
log_object = LogMessage.objects.create(
build = log_information['build'],
level = log_information['level'],
message = log_information['message'])
for v in vars(log_object):
if v in log_information.keys():
vars(log_object)[v] = log_information[v]
return log_object.save()
def save_build_package_information(self, build_obj, package_info, recipes):
# create and save the object
bp_object = Build_Package.objects.create( build = build_obj,
recipe = recipes[package_info['PN']],
name = package_info['PKG'],
version = package_info['PKGV'],
revision = package_info['PKGR'],
summary = package_info['SUMMARY'],
description = package_info['DESCRIPTION'],
size = int(package_info['PKGSIZE']) * 1024,
section = package_info['SECTION'],
license = package_info['LICENSE'],
)
# save any attached file information
for path in package_info['FILES_INFO']:
fo = Build_File.objects.create( bpackage = bp_object,
path = path,
size = package_info['FILES_INFO'][path] )
# save soft dependency information
if 'RDEPENDS' in package_info and package_info['RDEPENDS']:
for p in bb.utils.explode_deps(package_info['RDEPENDS']):
Build_Package_Dependency.objects.get_or_create( package = bp_object,
depends_on = p, dep_type = Build_Package_Dependency.TYPE_RDEPENDS)
if 'RPROVIDES' in package_info and package_info['RPROVIDES']:
for p in bb.utils.explode_deps(package_info['RPROVIDES']):
Build_Package_Dependency.objects.get_or_create( package = bp_object,
depends_on = p, dep_type = Build_Package_Dependency.TYPE_RPROVIDES)
if 'RRECOMMENDS' in package_info and package_info['RRECOMMENDS']:
for p in bb.utils.explode_deps(package_info['RRECOMMENDS']):
Build_Package_Dependency.objects.get_or_create( package = bp_object,
depends_on = p, dep_type = Build_Package_Dependency.TYPE_RRECOMMENDS)
if 'RSUGGESTS' in package_info and package_info['RSUGGESTS']:
for p in bb.utils.explode_deps(package_info['RSUGGESTS']):
Build_Package_Dependency.objects.get_or_create( package = bp_object,
depends_on = p, dep_type = Build_Package_Dependency.TYPE_RSUGGESTS)
if 'RREPLACES' in package_info and package_info['RREPLACES']:
for p in bb.utils.explode_deps(package_info['RREPLACES']):
Build_Package_Dependency.objects.get_or_create( package = bp_object,
depends_on = p, dep_type = Build_Package_Dependency.TYPE_RREPLACES)
if 'RCONFLICTS' in package_info and package_info['RCONFLICTS']:
for p in bb.utils.explode_deps(package_info['RCONFLICTS']):
Build_Package_Dependency.objects.get_or_create( package = bp_object,
depends_on = p, dep_type = Build_Package_Dependency.TYPE_RCONFLICTS)
return bp_object
def save_build_variables(self, build_obj, vardump):
for k in vardump:
if not bool(vardump[k]['func']):
value = vardump[k]['v'];
if value is None:
value = ''
desc = vardump[k]['doc'];
if desc is None:
var_words = [word for word in k.split('_')]
root_var = "_".join([word for word in var_words if word.isupper()])
if root_var and root_var != k and root_var in vardump:
desc = vardump[root_var]['doc']
if desc is None:
desc = ''
variable_obj = Variable.objects.create( build = build_obj,
variable_name = k,
variable_value = value,
description = desc)
for vh in vardump[k]['history']:
VariableHistory.objects.create( variable = variable_obj,
file_name = vh['file'],
line_number = vh['line'],
operation = vh['op'])
class BuildInfoHelper(object):
""" This class gathers the build information from the server and sends it
towards the ORM wrapper for storing in the database
It is instantiated once per build
Keeps in memory all data that needs matching before writing it to the database
"""
def __init__(self, server, has_build_history = False):
self._configure_django()
self.internal_state = {}
self.task_order = 0
self.server = server
self.orm_wrapper = ORMWrapper()
self.has_build_history = has_build_history
self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0]
def _configure_django(self):
# Add toaster to sys path for importing modules
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'toaster'))
###################
## methods to convert event/external info into objects that the ORM layer uses
def _get_layer_dict(self, layer_path):
layer_info = {}
layer_name = layer_path.split('/')[-1]
layer_url = 'http://layers.openembedded.org/layerindex/layer/{layer}/'
layer_url_name = self._get_url_map_name(layer_name)
layer_info['name'] = layer_name
layer_info['local_path'] = layer_path
layer_info['layer_index_url'] = layer_url.format(layer=layer_url_name)
return layer_info
def _get_url_map_name(self, layer_name):
""" Some layers have a different name on openembedded.org site,
this method returns the correct name to use in the URL
"""
url_name = layer_name
url_mapping = {'meta': 'openembedded-core'}
for key in url_mapping.keys():
if key == layer_name:
url_name = url_mapping[key]
return url_name
def _get_layer_information(self):
layer_info = {}
return layer_info
def _get_layer_version_information(self, layer_object):
layer_version_info = {}
layer_version_info['build'] = self.internal_state['build']
layer_version_info['layer'] = layer_object
layer_version_info['branch'] = self._get_git_branch(layer_object.local_path)
layer_version_info['commit'] = self._get_git_revision(layer_object.local_path)
layer_version_info['priority'] = 0
return layer_version_info
def _get_git_branch(self, layer_path):
branch = subprocess.Popen("git symbolic-ref HEAD 2>/dev/null ", cwd=layer_path, shell=True, stdout=subprocess.PIPE).communicate()[0]
branch = branch.replace('refs/heads/', '').rstrip()
return branch
def _get_git_revision(self, layer_path):
revision = subprocess.Popen("git rev-parse HEAD 2>/dev/null ", cwd=layer_path, shell=True, stdout=subprocess.PIPE).communicate()[0].rstrip()
return revision
def _get_build_information(self):
build_info = {}
# Generate an identifier for each new build
build_info['machine'] = self.server.runCommand(["getVariable", "MACHINE"])[0]
build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0]
build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0]
build_info['started_on'] = datetime.datetime.now()
build_info['completed_on'] = datetime.datetime.now()
build_info['image_fstypes'] = self._remove_redundant(self.server.runCommand(["getVariable", "IMAGE_FSTYPES"])[0] or "")
build_info['cooker_log_path'] = self.server.runCommand(["getVariable", "BB_CONSOLELOG"])[0]
build_info['build_name'] = self.server.runCommand(["getVariable", "BUILDNAME"])[0]
build_info['bitbake_version'] = self.server.runCommand(["getVariable", "BB_VERSION"])[0]
return build_info
def _get_task_information(self, event, recipe):
task_information = {}
task_information['build'] = self.internal_state['build']
task_information['outcome'] = Task.OUTCOME_NA
task_information['recipe'] = recipe
task_information['task_name'] = event.taskname
try:
# some tasks don't come with a hash. and that's ok
task_information['sstate_checksum'] = event.taskhash
except AttributeError:
pass
return task_information
def _get_layer_version_for_path(self, path):
def _slkey(layer_version):
return len(layer_version.layer.local_path)
# Heuristics: we always match recipe to the deepest layer path that
# we can match to the recipe file path
for bl in sorted(self.internal_state['layer_versions'], reverse=True, key=_slkey):
if (path.startswith(bl.layer.local_path)):
return bl
#TODO: if we get here, we didn't read layers correctly
assert False
return None
def _get_recipe_information_from_build_event(self, event):
layer_version_obj = self._get_layer_version_for_path(re.split(':', event.taskfile)[-1])
recipe_info = {}
recipe_info['layer_version'] = layer_version_obj
recipe_info['file_path'] = re.split(':', event.taskfile)[-1]
return recipe_info
def _get_task_build_stats(self, task_object):
bs_path = self._get_path_information(task_object)
for bp in bs_path: # TODO: split for each target
task_build_stats = self._get_build_stats_from_file(bp, task_object.task_name)
return task_build_stats
def _get_path_information(self, task_object):
build_stats_format = "{tmpdir}/buildstats/{target}-{machine}/{buildname}/{package}/"
build_stats_path = []
for t in self.internal_state['targets']:
target = t.target
machine = self.internal_state['build'].machine
buildname = self.internal_state['build'].build_name
pe, pv = task_object.recipe.version.split(":",1)
if len(pe) > 0:
package = task_object.recipe.name + "-" + pe + "_" + pv
else:
package = task_object.recipe.name + "-" + pv
build_stats_path.append(build_stats_format.format(tmpdir=self.tmp_dir, target=target,
machine=machine, buildname=buildname,
package=package))
return build_stats_path
def _get_build_stats_from_file(self, bs_path, task_name):
task_bs_filename = str(bs_path) + str(task_name)
task_bs = open(task_bs_filename, 'r')
cpu_usage = 0
disk_io = 0
startio = ''
endio = ''
for line in task_bs.readlines():
if line.startswith('CPU usage: '):
cpu_usage = line[11:]
elif line.startswith('EndTimeIO: '):
endio = line[11:]
elif line.startswith('StartTimeIO: '):
startio = line[13:]
task_bs.close()
if startio and endio:
disk_io = int(endio.strip('\n ')) - int(startio.strip('\n '))
if cpu_usage:
cpu_usage = float(cpu_usage.strip('% \n'))
task_build_stats = {'cpu_usage': cpu_usage, 'disk_io': disk_io}
return task_build_stats
def _remove_redundant(self, string):
ret = []
for i in string.split():
if i not in ret:
ret.append(i)
return " ".join(ret)
################################
## external available methods to store information
def store_layer_info(self):
layers = self.server.runCommand(["getVariable", "BBLAYERS"])[0].strip().split(" ")
self.internal_state['layers'] = []
for layer_path in { l for l in layers if len(l) }:
layer_information = self._get_layer_dict(layer_path)
self.internal_state['layers'].append(self.orm_wrapper.get_update_layer_object(layer_information))
def store_started_build(self, event):
build_information = self._get_build_information()
build_obj = self.orm_wrapper.create_build_object(build_information)
self.internal_state['build'] = build_obj
# create target information
target_information = {}
target_information['targets'] = event.getPkgs()
target_information['build'] = build_obj
self.internal_state['targets'] = self.orm_wrapper.create_target_objects(target_information)
# Load layer information for the build
self.internal_state['layer_versions'] = []
for layer_object in self.internal_state['layers']:
layer_version_information = self._get_layer_version_information(layer_object)
self.internal_state['layer_versions'].append(self.orm_wrapper.get_layer_version_object(layer_version_information))
del self.internal_state['layers']
# Save build configuration
self.orm_wrapper.save_build_variables(build_obj, self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0])
def update_build_information(self, event, errors, warnings, taskfailures):
if 'build' in self.internal_state:
self.orm_wrapper.update_build_object(self.internal_state['build'], errors, warnings, taskfailures)
def store_started_task(self, event):
identifier = event.taskfile + event.taskname
recipe_information = self._get_recipe_information_from_build_event(event)
recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
task_information = self._get_task_information(event, recipe)
task_information['outcome'] = Task.OUTCOME_NA
if isinstance(event, bb.runqueue.runQueueTaskSkipped):
task_information['task_executed'] = False
if event.reason == "covered":
task_information['outcome'] = Task.OUTCOME_COVERED
if event.reason == "existing":
task_information['outcome'] = Task.OUTCOME_EXISTING
else:
task_information['task_executed'] = True
if 'noexec' in vars(event) and event.noexec == True:
task_information['script_type'] = Task.CODING_NOEXEC
self.task_order += 1
task_information['order'] = self.task_order
task_obj = self.orm_wrapper.get_update_task_object(task_information)
self.internal_state[identifier] = {'start_time': datetime.datetime.now()}
def update_and_store_task(self, event):
identifier = event.taskfile + event.taskname
recipe_information = self._get_recipe_information_from_build_event(event)
recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
task_information = self._get_task_information(event,recipe)
try:
task_information['start_time'] = self.internal_state[identifier]['start_time']
except:
pass
if 'logfile' in vars(event):
task_information['logfile'] = event.logfile
if '_message' in vars(event):
task_information['message'] = event._message
if 'taskflags' in vars(event):
# with TaskStarted, we get even more information
if 'python' in event.taskflags.keys() and event.taskflags['python'] == '1':
task_information['script_type'] = Task.CODING_PYTHON
else:
task_information['script_type'] = Task.CODING_SHELL
if isinstance(event, (bb.runqueue.runQueueTaskCompleted, bb.runqueue.sceneQueueTaskCompleted)):
task_information['outcome'] = Task.OUTCOME_SUCCESS
task_build_stats = self._get_task_build_stats(self.orm_wrapper.get_update_task_object(task_information))
task_information['cpu_usage'] = task_build_stats['cpu_usage']
task_information['disk_io'] = task_build_stats['disk_io']
del self.internal_state[identifier]
if isinstance(event, (bb.runqueue.runQueueTaskFailed, bb.runCommand.sceneQueueTaskFailed)):
task_information['outcome'] = Task.OUTCOME_FAILED
del self.internal_state[identifier]
self.orm_wrapper.get_update_task_object(task_information)
def read_target_package_dep_data(self, event):
# for all targets
for target in self.internal_state['targets']:
# verify that we have something to read
if not target.is_image or not self.has_build_history:
print "not collecting package info ", target.is_image, self.has_build_history
break
# TODO this is a temporary replication of the code in buildhistory.bbclass
# This MUST be changed to query the actual BUILD_DIR_IMAGE in the target context when
# the capability will be implemented in Bitbake
MACHINE_ARCH, error = self.server.runCommand(['getVariable', 'MACHINE_ARCH'])
TCLIBC, error = self.server.runCommand(['getVariable', 'TCLIBC'])
BUILDHISTORY_DIR, error = self.server.runCommand(['getVariable', 'BUILDHISTORY_DIR'])
BUILDHISTORY_DIR_IMAGE = "%s/images/%s/%s/%s" % (BUILDHISTORY_DIR, MACHINE_ARCH, TCLIBC, target.target)
self.internal_state['packages'] = {}
with open("%s/installed-package-sizes.txt" % BUILDHISTORY_DIR_IMAGE, "r") as fin:
for line in fin:
line = line.rstrip(";")
psize, px = line.split("\t")
punit, pname = px.split(" ")
self.internal_state['packages'][pname.strip()] = {'size':int(psize)*1024, 'depends' : []}
with open("%s/depends.dot" % BUILDHISTORY_DIR_IMAGE, "r") as fin:
p = re.compile(r' -> ')
dot = re.compile(r'.*style=dotted')
for line in fin:
line = line.rstrip(';')
linesplit = p.split(line)
if len(linesplit) == 2:
pname = linesplit[0].rstrip('"').strip('"')
dependsname = linesplit[1].split(" ")[0].strip().strip(";").strip('"').rstrip('"')
deptype = Target_Package_Dependency.TYPE_DEPENDS
if dot.match(line):
deptype = Target_Package_Dependency.TYPE_RECOMMENDS
if not pname in self.internal_state['packages']:
self.internal_state['packages'][pname] = {'size': 0, 'depends' : []}
if not dependsname in self.internal_state['packages']:
self.internal_state['packages'][dependsname] = {'size': 0, 'depends' : []}
self.internal_state['packages'][pname]['depends'].append((dependsname, deptype))
self.orm_wrapper.save_target_package_information(target,
self.internal_state['packages'],
self.internal_state['bldpkgs'], self.internal_state['recipes'])
def store_dependency_information(self, event):
# save layer version priorities
if 'layer-priorities' in event._depgraph.keys():
for lv in event._depgraph['layer-priorities']:
(name, path, regexp, priority) = lv
layer_version_obj = self._get_layer_version_for_path(path[1:]) # paths start with a ^
assert layer_version_obj is not None
layer_version_obj.priority = priority
layer_version_obj.save()
# save build time package information
self.internal_state['bldpkgs'] = {}
for pkg in event._depgraph['packages']:
self.internal_state['bldpkgs'][pkg] = event._depgraph['packages'][pkg]
# save recipe information
self.internal_state['recipes'] = {}
for pn in event._depgraph['pn']:
file_name = re.split(':', event._depgraph['pn'][pn]['filename'])[-1]
layer_version_obj = self._get_layer_version_for_path(re.split(':', file_name)[-1])
assert layer_version_obj is not None
recipe_info = {}
recipe_info['name'] = pn
recipe_info['version'] = event._depgraph['pn'][pn]['version']
recipe_info['layer_version'] = layer_version_obj
recipe_info['summary'] = event._depgraph['pn'][pn]['summary']
recipe_info['license'] = event._depgraph['pn'][pn]['license']
recipe_info['description'] = event._depgraph['pn'][pn]['description']
recipe_info['section'] = event._depgraph['pn'][pn]['section']
recipe_info['licensing_info'] = 'Not Available'
recipe_info['homepage'] = event._depgraph['pn'][pn]['homepage']
recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker']
recipe_info['file_path'] = file_name
recipe = self.orm_wrapper.get_update_recipe_object(recipe_info)
if 'inherits' in event._depgraph['pn'][pn].keys():
recipe.is_image = True in map(lambda x: x.endswith('image.bbclass'), event._depgraph['pn'][pn]['inherits'])
else:
recipe.is_image = False
if recipe.is_image:
for t in self.internal_state['targets']:
if pn == t.target:
t.is_image = True
t.save()
self.internal_state['recipes'][pn] = recipe
# save recipe dependency
# buildtime
for recipe in event._depgraph['depends']:
try:
target = self.internal_state['recipes'][recipe]
for dep in event._depgraph['depends'][recipe]:
dependency = self.internal_state['recipes'][dep]
Recipe_Dependency.objects.get_or_create( recipe = target,
depends_on = dependency, dep_type = Recipe_Dependency.TYPE_DEPENDS)
except KeyError: # we'll not get recipes for key w/ values listed in ASSUME_PROVIDED
pass
# runtime
for recipe in event._depgraph['rdepends-pn']:
try:
target = self.internal_state['recipes'][recipe]
for dep in event._depgraph['rdepends-pn'][recipe]:
dependency = self.internal_state['recipes'][dep]
Recipe_Dependency.objects.get_or_create( recipe = target,
depends_on = dependency, dep_type = Recipe_Dependency.TYPE_RDEPENDS)
except KeyError: # we'll not get recipes for key w/ values listed in ASSUME_PROVIDED
pass
# save all task information
def _save_a_task(taskdesc):
spec = re.split(r'\.', taskdesc);
pn = ".".join(spec[0:-1])
taskname = spec[-1]
e = event
e.taskname = pn
recipe = self.internal_state['recipes'][pn]
task_info = self._get_task_information(e, recipe)
task_info['task_name'] = taskname
task_obj = self.orm_wrapper.get_update_task_object(task_info)
return task_obj
for taskdesc in event._depgraph['tdepends']:
target = _save_a_task(taskdesc)
for taskdesc1 in event._depgraph['tdepends'][taskdesc]:
dep = _save_a_task(taskdesc1)
Task_Dependency.objects.get_or_create( task = target, depends_on = dep )
def store_build_package_information(self, event):
package_info = event.data
self.orm_wrapper.save_build_package_information(self.internal_state['build'],
package_info,
self.internal_state['recipes'],
)
def _store_log_information(self, level, text):
log_information = {}
log_information['build'] = self.internal_state['build']
log_information['level'] = level
log_information['message'] = text
self.orm_wrapper.create_logmessage(log_information)
def store_log_info(self, text):
self._store_log_information(LogMessage.INFO, text)
def store_log_warn(self, text):
self._store_log_information(LogMessage.WARNING, text)
def store_log_error(self, text):
self._store_log_information(LogMessage.ERROR, text)
def store_log_event(self, event):
# look up license files info from insane.bbclass
m = re.match("([^:]*): md5 checksum matched for ([^;]*)", event.msg)
if m:
(pn, fn) = m.groups()
self.internal_state['recipes'][pn].licensing_info = fn
self.internal_state['recipes'][pn].save()
if event.levelno < format.WARNING:
return
if not 'build' in self.internal_state:
return
log_information = {}
log_information['build'] = self.internal_state['build']
if event.levelno >= format.ERROR:
log_information['level'] = LogMessage.ERROR
elif event.levelno == format.WARNING:
log_information['level'] = LogMessage.WARNING
log_information['message'] = event.msg
log_information['pathname'] = event.pathname
log_information['lineno'] = event.lineno
self.orm_wrapper.create_logmessage(log_information)

View File

@@ -0,0 +1,272 @@
#
# BitBake ToasterUI Implementation
# based on (No)TTY UI Implementation by Richard Purdie
#
# Handling output to TTYs or files (no TTY)
#
# Copyright (C) 2006-2012 Richard Purdie
# Copyright (C) 2013 Intel Corporation
#
# 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.
from __future__ import division
try:
import bb
except RuntimeError as exc:
sys.exit(str(exc))
from bb.ui import uihelper
from bb.ui.buildinfohelper import BuildInfoHelper
import bb.msg
import copy
import fcntl
import logging
import os
import progressbar
import signal
import struct
import sys
import time
import xmlrpclib
featureSet = [bb.cooker.CookerFeatures.HOB_EXTRA_CACHES, bb.cooker.CookerFeatures.SEND_DEPENDS_TREE, bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING]
logger = logging.getLogger("BitBake")
interactive = sys.stdout.isatty()
def _log_settings_from_server(server):
# Get values of variables which control our output
includelogs, error = server.runCommand(["getVariable", "BBINCLUDELOGS"])
if error:
logger.error("Unable to get the value of BBINCLUDELOGS variable: %s" % error)
raise BaseException(error)
loglines, error = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
if error:
logger.error("Unable to get the value of BBINCLUDELOGS_LINES variable: %s" % error)
raise BaseException(error)
return includelogs, loglines
def main(server, eventHandler, params ):
includelogs, loglines = _log_settings_from_server(server)
# verify and warn
build_history_enabled = True
inheritlist, error = server.runCommand(["getVariable", "INHERIT"])
if not "buildhistory" in inheritlist.split(" "):
logger.warn("buildhistory is not enabled. Please enable INHERIT += \"buildhistory\" to see image details.")
build_history_enabled = False
helper = uihelper.BBUIHelper()
console = logging.StreamHandler(sys.stdout)
format_str = "%(levelname)s: %(message)s"
format = bb.msg.BBLogFormatter(format_str)
bb.msg.addDefaultlogFilter(console)
console.setFormatter(format)
logger.addHandler(console)
if not params.observe_only:
logger.error("ToasterUI can only work in observer mode")
return
main.shutdown = 0
interrupted = False
return_value = 0
errors = 0
warnings = 0
taskfailures = []
buildinfohelper = BuildInfoHelper(server, build_history_enabled)
buildinfohelper.store_layer_info()
while True:
try:
event = eventHandler.waitEvent(0.25)
if event is None:
if main.shutdown > 0:
break
continue
helper.eventHandler(event)
if isinstance(event, bb.event.BuildStarted):
buildinfohelper.store_started_build(event)
if isinstance(event, (bb.build.TaskStarted, bb.build.TaskSucceeded, bb.build.TaskFailedSilent)):
buildinfohelper.update_and_store_task(event)
continue
if isinstance(event, bb.event.LogExecTTY):
logger.warn(event.msg)
continue
if isinstance(event, logging.LogRecord):
buildinfohelper.store_log_event(event)
if event.levelno >= format.ERROR:
errors = errors + 1
return_value = 1
elif event.levelno == format.WARNING:
warnings = warnings + 1
# For "normal" logging conditions, don't show note logs from tasks
# but do show them if the user has changed the default log level to
# include verbose/debug messages
if event.taskpid != 0 and event.levelno <= format.NOTE:
continue
logger.handle(event)
continue
if isinstance(event, bb.build.TaskFailed):
buildinfohelper.update_and_store_task(event)
return_value = 1
logfile = event.logfile
if logfile and os.path.exists(logfile):
bb.error("Logfile of failure stored in: %s" % logfile)
continue
# these events are unprocessed now, but may be used in the future to log
# timing and error informations from the parsing phase in Toaster
if isinstance(event, bb.event.ParseStarted):
continue
if isinstance(event, bb.event.ParseProgress):
continue
if isinstance(event, bb.event.ParseCompleted):
continue
if isinstance(event, bb.event.CacheLoadStarted):
continue
if isinstance(event, bb.event.CacheLoadProgress):
continue
if isinstance(event, bb.event.CacheLoadCompleted):
continue
if isinstance(event, bb.event.MultipleProviders):
continue
if isinstance(event, bb.event.NoProvider):
return_value = 1
errors = errors + 1
if event._runtime:
r = "R"
else:
r = ""
if event._dependees:
text = "Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)" % (r, event._item, ", ".join(event._dependees), r)
else:
text = "Nothing %sPROVIDES '%s'" % (r, event._item)
logger.error(text)
if event._reasons:
for reason in event._reasons:
logger.error("%s", reason)
text += reason
buildinfohelper.store_log_error(text)
continue
if isinstance(event, bb.event.ConfigParsed):
continue
if isinstance(event, bb.event.RecipeParsed):
continue
# end of saved events
if isinstance(event, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped)):
buildinfohelper.store_started_task(event)
continue
if isinstance(event, bb.runqueue.runQueueTaskCompleted):
buildinfohelper.update_and_store_task(event)
continue
if isinstance(event, bb.runqueue.runQueueTaskFailed):
buildinfohelper.update_and_store_task(event)
taskfailures.append(event.taskstring)
logger.error("Task %s (%s) failed with exit code '%s'",
event.taskid, event.taskstring, event.exitcode)
continue
if isinstance(event, (bb.runqueue.sceneQueueTaskCompleted, bb.runqueue.sceneQueueTaskFailed)):
buildinfohelper.update_and_store_task(event)
continue
if isinstance(event, (bb.event.TreeDataPreparationStarted, bb.event.TreeDataPreparationCompleted)):
continue
if isinstance(event, (bb.event.BuildCompleted)):
buildinfohelper.read_target_package_dep_data(event)
buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
continue
if isinstance(event, (bb.command.CommandCompleted,
bb.command.CommandFailed,
bb.command.CommandExit)):
buildinfohelper.update_build_information(event, errors, warnings, taskfailures)
# we start a new build info
errors = 0
warnings = 0
taskfailures = []
buildinfohelper = BuildInfoHelper(server, build_history_enabled)
buildinfohelper.store_layer_info()
continue
if isinstance(event, bb.event.MetadataEvent):
if event.type == "SinglePackageInfo":
buildinfohelper.store_build_package_information(event)
continue
# ignore
if isinstance(event, (bb.event.BuildBase,
bb.event.StampUpdate,
bb.event.RecipePreFinalise,
bb.runqueue.runQueueEvent,
bb.runqueue.runQueueExitWait,
bb.event.OperationProgress,
bb.command.CommandFailed,
bb.command.CommandExit,
bb.command.CommandCompleted,
bb.cooker.CookerExit)):
continue
if isinstance(event, bb.event.DepTreeGenerated):
buildinfohelper.store_dependency_information(event)
continue
logger.error("Unknown event: %s", event)
except EnvironmentError as ioerror:
# ignore interrupted io
if ioerror.args[0] == 4:
pass
except KeyboardInterrupt:
main.shutdown = 1
pass
except Exception as e:
logger.error(e)
import traceback
traceback.print_exc()
pass
if interrupted:
if return_value == 0:
return_value = 1
return return_value

View File

View File

@@ -0,0 +1,37 @@
#
# BitBake Toaster Implementation
#
# Copyright (C) 2013 Intel Corporation
#
# 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.
from django.conf.urls import patterns, include, url
urlpatterns = patterns('bldviewer.views',
url(r'^builds$', 'model_explorer', {'model_name':'build'}, name='builds'),
url(r'^targets$', 'model_explorer', {'model_name':'target'}, name='targets'),
url(r'^tasks$', 'model_explorer', {'model_name':'task'}, name='task'),
url(r'^task_dependencies$', 'model_explorer', {'model_name':'task_dependency'}, name='task_dependencies'),
url(r'^packages$', 'model_explorer', {'model_name':'build_package'}, name='build_packages'),
url(r'^package_dependencies$', 'model_explorer', {'model_name':'build_package_dependency'}, name='build_package_dependencies'),
url(r'^target_packages$', 'model_explorer', {'model_name':'target_package'}, name='target_packages'),
url(r'^package_files$', 'model_explorer', {'model_name':'build_file'}, name='build_files'),
url(r'^layers$', 'model_explorer', {'model_name':'layer'}, name='layer'),
url(r'^layerversions$', 'model_explorer', {'model_name':'layerversion'}, name='layerversion'),
url(r'^recipes$', 'model_explorer', {'model_name':'recipe'}, name='recipe'),
url(r'^recipe_dependencies$', 'model_explorer', {'model_name':'recipe_dependency'}, name='recipe_dependencies'),
url(r'^variables$', 'model_explorer', {'model_name':'variable'}, name='variables'),
url(r'^logmessages$', 'model_explorer', {'model_name':'logmessage'}, name='logmessages'),
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
{% load static %}
<html>
<head>
<title>Toaster Simple Explorer</title>
<script src="{% static 'js/jquery-2.0.3.js' %}">
</script>
<script src="{% static 'js/bootstrap.js' %}">
</script>
<link href="{% static 'css/bootstrap.css' %}" rel="stylesheet" type="text/css">
</head>
<body style="height: 100%">
<div style="width:100%; height: 100%; position:absolute">
<div style="width: 100%; height: 3em" class="nav">
<ul class="nav nav-tabs">
<li><a href="{% url all-builds %}">All Builds</a></li>
<li><a href="{% url all-layers %}">All Layers</a></li>
</ul>
</div>
<div style="overflow-y:scroll; width: 100%; position: absolute; top: 3em; bottom:70px ">
{% block pagecontent %}
{% endblock %}
</div>
<div class="navbar" style="position: absolute; bottom: 0; width:100%"><br/>About Toaster | Yocto Project </div>
</div>
</body>
</html>

View File

@@ -0,0 +1,17 @@
{% extends "basetable.html" %}
{% block pagename %}
<ul class="nav nav-tabs" style="display: inline-block">
<li><a>Build {{build.target_set.all|join:"&nbsp;"}} at {{build.started_on}} : </a></li>
<li><a href="{% url task build.id %}"> Tasks </a></li>
<li><a href="{% url bpackage build.id %}"> Build Packages </a></li>
{% for t in build.target_set.all %}
{% if t.is_image %}
<li><a href="{% url tpackage build.id t.pk %}"> Packages for {{t.target}} </a> </li>
{% endif %}
{% endfor %}
<li><a href="{% url configuration build.id %}"> Configuration </a> </li>
</ul>
<h1>Toaster - Build {% block pagetitle %} {% endblock %}</h1>
{% endblock %}

View File

@@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block pagecontent %}
<script>
function showhideTableColumn(i, sh) {
if (sh)
$('td:nth-child('+i+'),th:nth-child('+i+')').show();
else
$('td:nth-child('+i+'),th:nth-child('+i+')').hide();
}
function filterTableRows(test) {
if (test.length > 0) {
var r = test.split(/[ ,]+/).map(function (e) { return new RegExp(e, 'i') });
$('tr.data').map( function (i, el) {
(! r.map(function (j) { return j.test($(el).html())}).reduce(function (c, p) { return c && p;} )) ? $(el).hide() : $(el).show();
});
} else
{
$('tr.data').show();
}
}
</script>
<div style="margin-bottom: 0.5em">
{% block pagename %}
{% endblock %}
<div align="left" style="display:inline-block; width: 40%; margin-left: 2em"> Search: <input type="search" id="filterstring" style="width: 80%" onkeyup="filterTableRows($('#filterstring').val())" autocomplete="off">
</div>
{% if hideshowcols %}
<div align="right" style="display: inline-block; width: 40%">Show/Hide columns:
{% for i in hideshowcols %}
<span>{{i.name}} <input type="checkbox" id="ct{{i.name}}" onchange="showhideTableColumn({{i.order}}, $('#ct{{i.name}}').is(':checked'))" checked autocomplete="off"></span> |
{% endfor %}
</div>
{% endif %}
</div>
<div>
<table class="table table-striped table-condensed" style="width:95%">
{% block pagetable %}
{% endblock %}
</table>
</div>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends "basebuildpage.html" %}
{% block pagetitle %}Files for package {{files.0.bpackage.name}} {% endblock %}
{% block pagetable %}
{% if not files %}
<p>No files were recorded for this package!</p>
{% else %}
<tr>
<th>Name</th>
<th>Size (Bytes)</th>
</tr>
{% for file in files %}
<tr class="data">
<td>{{file.path}}</td>
<td>{{file.size}}</td>
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,44 @@
{% extends "basebuildpage.html" %}
{% block pagetitle %}Packages{% endblock %}
{% block pagetable %}
{% if not packages %}
<p>No packages were recorded for this target!</p>
{% else %}
<tr>
<th>Name</th>
<th>Version</th>
<th>Recipe</th>
<th>Summary</th>
<th>Section</th>
<th>Description</th>
<th>Size on host disk (Bytes)</th>
<th>License</th>
<th>Dependencies List (all)</th>
</tr>
{% for package in packages %}
<tr class="data">
<td><a name="#{{package.name}}" href="{% url bfile build.pk package.pk %}">{{package.name}} ({{package.filelist_bpackage.count}} files)</a></td>
<td>{{package.version}}-{{package.revision}}</td>
<td><a href="{% url layer_versions_recipes package.recipe.layer_version_id %}#{{package.recipe.name}}">{{package.recipe.name}}</a>{{package.package_name}}</a></td>
<td>{{package.summary}}</td>
<td>{{package.section}}</td>
<td>{{package.description}}</td>
<td>{{package.size}}</td>
<td>{{package.license}}</td>
<td>
<div style="height: 3em; overflow:auto">
{% for bpd in package.bpackage_dependencies_package.all %}
{{bpd.dep_type}}: {{bpd.depends_on}} <br/>
{% endfor %}
</div>
</td>
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends "basetable.html" %}
{% block pagename %}
<h1>Toaster - Builds</h1>
{% endblock %}
{% block pagetable %}
{% load projecttags %}
<tr>
<th>Outcome</th>
<th>Started On</th>
<th>Completed On</th>
<th>Target</th>
<th>Machine</th>
<th>Time</th>
<th>Errors</th>
<th>Warnings</th>
<th>Output</th>
<th>Log</th>
<th>Bitbake Version</th>
<th>Build Name</th>
</tr>
{% for build in builds %}
<tr class="data">
<td><a href="{% url configuration build.id %}">{{build.get_outcome_display}}</a></td>
<td>{{build.started_on}}</td>
<td>{{build.completed_on}}</td>
<td>{% for t in build.target_set.all %}<a href="{% url tpackage build.id t.id %}">{{t.target}}</a>{% if t.is_image %} (Img){% endif %}<br/>{% endfor %}</td>
<td>{{build.machine}}</td>
<td>{% time_difference build.started_on build.completed_on %}</td>
<td>{{build.errors_no}}:{% if build.errors_no %}{% for error in logs %}{% if error.build == build %}{% if error.level == 2 %}<p>{{error.message}}</p>{% endif %}{% endif %}{% endfor %}{% else %}None{% endif %}</td>
<td>{{build.warnings_no}}:{% if build.warnings_no %}{% for warning in logs %}{% if warning.build == build %}{% if warning.level == 1 %}<p>{{warning.message}}</p>{% endif %}{% endif %}{% endfor %}{% else %}None{% endif %}</td>
<td>{% if build.outcome == 0 %}{% for t in build.target_set.all %}{% if t.is_image %}{{build.image_fstypes}}{% endif %}{% endfor %}{% endif %}</td>
<td>{{build.cooker_log_path}}</td>
<td>{{build.bitbake_version}}</td>
<td>{{build.build_name}}</td>
</tr>
{% endfor %}
{% endblock %}

View File

@@ -0,0 +1,22 @@
{% extends "basebuildpage.html" %}
{% block pagetitle %}Configuration{% endblock %}
{% block pagetable %}
<tr>
<th>Name</th>
<th>Description</th>
<th>Definition history</th>
<th>Value</th>
</tr>
{% for variable in configuration %}
<tr class="data">
<td>{{variable.variable_name}}</td>
<td>{% if variable.description %}{{variable.description}}{% endif %}</td>
<td>{% for vh in variable.variablehistory_set.all %}{{vh.operation}} in {{vh.file_name}}:{{vh.line_number}}<br/>{%endfor%}</td>
<td>{{variable.variable_value}}</td>
{% endfor %}
{% endblock %}

View File

@@ -0,0 +1,34 @@
{% extends "basetable.html" %}
{% block pagename %}
<h1>Toaster - Layers</h1>
{% endblock %}
{% block pagetable %}
{% load projecttags %}
<tr>
<th>Name</th>
<th>Local Path</th>
<th>Layer Index URL</th>
<th>Known Versions</th>
</tr>
{% for layer in layers %}
<tr class="data">
<td>{{layer.name}}</td>
<td>{{layer.local_path}}</td>
<td><a href='{{layer.layer_index_url}}'>{{layer.layer_index_url}}</a></td>
<td><table>
{% for lv in layer.versions %}
<tr><td>
<a href="{% url layer_versions_recipes lv.id %}">({{lv.priority}}){{lv.branch}}:{{lv.commit}} ({{lv.count}} recipes)</a>
</td></tr>
{% endfor %}
</table></td>
</tr>
{% endfor %}
{% endblock %}

View File

@@ -0,0 +1,36 @@
{% extends "basebuildpage.html" %}
{% block pagetable %}
{% if not packages %}
<p>No packages were recorded for this target!</p>
{% else %}
<tr>
<th>Name</th>
<th>Version</th>
<th>Size (Bytes)</th>
<th>Recipe</th>
<th>Depends on</th>
</tr>
{% for package in packages %}
<tr class="data">
<td><a name="#{{package.name}}">{{package.name}}</a></td>
<td>{{package.version}}</td>
<td>{{package.size}}</td>
<td>{%if package.recipe %}<a name="{{package.recipe.name}}.{{package.package_name}}">
<a href="{% url layer_versions_recipes package.recipe.layer_version_id %}#{{package.recipe.name}}">{{package.recipe.name}}</a>{{package.package_name}}</a>{%endif%}</td>
<td>
<div style="height: 4em; overflow:auto">
{% for d in package.tpackage_dependencies_package.all %}
<a href="#{{d.name}}">{{d.depends_on.name}}</a><br/>
{% endfor %}
</div>
</td>
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,53 @@
{% extends "basetable.html" %}
{% block pagename %}
<ul class="nav nav-tabs" style="display: inline-block">
<li><a>Layer {{layer_version.layer.name}}&nbsp;:&nbsp;{{layer_version.branch}}&nbsp;:&nbsp;{{layer_version.commit}}&nbsp;:&nbsp;{{layer_version.priority}}</a></li>
</ul>
<h1>Toaster - Recipes for a Layer</h1>
{% endblock %}
{% block pagetable %}
{% load projecttags %}
<tr>
</tr>
<th>Name</th>
<th>Version</th>
<th>Summary</th>
<th>Description</th>
<th>Section</th>
<th>License</th>
<th>License file</th>
<th>Homepage</th>
<th>Bugtracker</th>
<th>Author</th>
<th>File_path</th>
<th style="width: 30em">Recipe Dependency</th>
{% for recipe in recipes %}
<tr class="data">
<td><a name="{{recipe.name}}">{{recipe.name}}</a></td>
<td>{{recipe.version}}</td>
<td>{{recipe.summary}}</td>
<td>{{recipe.description}}</td>
<td>{{recipe.section}}</td>
<td>{{recipe.license}}</td>
<td>{{recipe.licensing_info}}</td>
<td>{{recipe.homepage}}</td>
<td>{{recipe.bugtracker}}</td>
<td>{{recipe.file_path}}</td>
<td>
<div style="height: 5em; overflow:auto">
{% for rr in recipe.r_dependencies_recipe.all %}
<a href="#{{rr.depends_on.name}}">{{rr.depends_on.name}}</a><br/>
{% endfor %}
</div>
</td>
</tr>
{% endfor %}
{% endblock %}

View File

@@ -0,0 +1,63 @@
{% extends "basebuildpage.html" %}
{% block pagetitle %}Tasks{% endblock %}
{% block pagetable %}
{% if not tasks %}
<p>No tasks were executed in this build!</p>
{% else %}
<tr>
<th>Order</th>
<th>Task</th>
<th>Recipe Version</th>
<th>Task Type</th>
<th>Checksum</th>
<th>Outcome</th>
<th>Message</th>
<th>Logfile</th>
<th>Time</th>
<th>CPU usage</th>
<th>Disk I/O</th>
<th>Script type</th>
<th>File path</th>
<th>Depends</th>
</tr>
{% for task in tasks %}
<tr class="data">
<td>{{task.order}}</td>
<td><a name="{{task.recipe.name}}.{{task.task_name}}">
<a href="{% url layer_versions_recipes task.recipe.layer_version_id %}#{{task.recipe.name}}">{{task.recipe.name}}</a>.{{task.task_name}}</a></td>
<td>{{task.recipe.version}}</td>
{% if task.task_executed %}
<td>Executed</td>
{% else %}
<td>Prebuilt</td>
{% endif %}
<td>{{task.sstate_checksum}}</td>
<td>{{task.get_outcome_display}}{% if task.provider %}</br>(by <a href="#{{task.provider.recipe.name}}.{{task.provider.task_name}}">{{task.provider.recipe.name}}.{{task.provider.task_name}}</a>){% endif %}</td>
<td><p>{{task.message}}</td>
<td><a target="_fileview" href="file:///{{task.logfile}}">{{task.logfile}}</a></td>
<td>{{task.elapsed_time}}</td>
<td>{{task.cpu_usage}}</td>
<td>{{task.disk_io}}</td>
<td>{{task.get_script_type_display}}</td>
<td><a target="_fileview" href="file:///{{task.recipe.file_path}}">{{task.recipe.file_path}}</a></td>
<td>
<div style="height: 3em; overflow:auto">
{% for tt in task.task_dependencies_task.all %}
<a href="#{{tt.depends_on.recipe.name}}.{{tt.depends_on.task_name}}">
{{tt.depends_on.recipe.name}}.{{tt.depends_on.task_name}}</a><br/>
{% endfor %}
</div>
</td>
</tr>
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,26 @@
#
# BitBake Toaster Implementation
#
# Copyright (C) 2013 Intel Corporation
#
# 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.
from datetime import datetime
from django import template
register = template.Library()
@register.simple_tag
def time_difference(start_time, end_time):
return end_time - start_time

View File

@@ -0,0 +1,32 @@
#
# BitBake Toaster Implementation
#
# Copyright (C) 2013 Intel Corporation
#
# 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.
from django.conf.urls import patterns, include, url
from django.views.generic.simple import redirect_to
urlpatterns = patterns('bldviewer.views',
url(r'^builds/$', 'build', name='all-builds'),
url(r'^build/(?P<build_id>\d+)/task/$', 'task', name='task'),
url(r'^build/(?P<build_id>\d+)/packages/$', 'bpackage', name='bpackage'),
url(r'^build/(?P<build_id>\d+)/package/(?P<package_id>\d+)/files/$', 'bfile', name='bfile'),
url(r'^build/(?P<build_id>\d+)/target/(?P<target_id>\d+)/packages/$', 'tpackage', name='tpackage'),
url(r'^build/(?P<build_id>\d+)/configuration/$', 'configuration', name='configuration'),
url(r'^layers/$', 'layer', name='all-layers'),
url(r'^layerversions/(?P<layerversion_id>\d+)/recipes/.*$', 'layer_versions_recipes', name='layer_versions_recipes'),
url(r'^$', redirect_to, {'url': 'builds/'}),
)

View File

@@ -0,0 +1,260 @@
#
# BitBake Toaster Implementation
#
# Copyright (C) 2013 Intel Corporation
#
# 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 operator
from django.db.models import Q
from django.shortcuts import render
from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, Target_Package, LogMessage, Variable
from orm.models import Task_Dependency, Recipe_Dependency, Build_Package, Build_File, Build_Package_Dependency
from django.views.decorators.cache import cache_control
@cache_control(no_store=True)
def build(request):
template = 'build.html'
build_info = Build.objects.all()
logs = LogMessage.objects.all()
context = {'builds': build_info, 'logs': logs ,
'hideshowcols' : [
{'name': 'Output', 'order':10},
{'name': 'Log', 'order':11},
]}
return render(request, template, context)
def _find_task_revdep(task):
tp = []
for p in Task_Dependency.objects.filter(depends_on=task):
tp.append(p.task);
return tp
def _find_task_provider(task):
task_revdeps = _find_task_revdep(task)
for tr in task_revdeps:
if tr.outcome != Task.OUTCOME_COVERED:
return tr
for tr in task_revdeps:
trc = _find_task_provider(tr)
if trc is not None:
return trc
return None
def task(request, build_id):
template = 'task.html'
tasks = Task.objects.filter(build=build_id)
for t in tasks:
if t.outcome == Task.OUTCOME_COVERED:
t.provider = _find_task_provider(t)
context = {'build': Build.objects.filter(pk=build_id)[0], 'tasks': tasks}
return render(request, template, context)
def configuration(request, build_id):
template = 'configuration.html'
variables = Variable.objects.filter(build=build_id)
context = {'build': Build.objects.filter(pk=build_id)[0], 'configuration' : variables}
return render(request, template, context)
def bpackage(request, build_id):
template = 'bpackage.html'
packages = Build_Package.objects.filter(build = build_id)
context = {'build': Build.objects.filter(pk=build_id)[0], 'packages' : packages}
return render(request, template, context)
def bfile(request, build_id, package_id):
template = 'bfile.html'
files = Build_File.objects.filter(bpackage = package_id)
context = {'build': Build.objects.filter(pk=build_id)[0], 'files' : files}
return render(request, template, context)
def tpackage(request, build_id, target_id):
template = 'package.html'
packages = Target_Package.objects.filter(target=target_id)
context = {'build' : Build.objects.filter(pk=build_id)[0],'packages': packages}
return render(request, template, context)
def layer(request):
template = 'layer.html'
layer_info = Layer.objects.all()
for li in layer_info:
li.versions = Layer_Version.objects.filter(layer = li)
for liv in li.versions:
liv.count = Recipe.objects.filter(layer_version__id = liv.id).count()
context = {'layers': layer_info}
return render(request, template, context)
def layer_versions_recipes(request, layerversion_id):
template = 'recipe.html'
recipes = Recipe.objects.filter(layer_version__id = layerversion_id)
context = {'recipes': recipes,
'layer_version' : Layer_Version.objects.filter( id = layerversion_id )[0]
}
return render(request, template, context)
#### API
import json
from django.core import serializers
from django.http import HttpResponse, HttpResponseBadRequest
def model_explorer(request, model_name):
DESCENDING = 'desc'
response_data = {}
model_mapping = {
'build': Build,
'target': Target,
'target_package': Target_Package,
'task': Task,
'task_dependency': Task_Dependency,
'package': Build_Package,
'layer': Layer,
'layerversion': Layer_Version,
'recipe': Recipe,
'recipe_dependency': Recipe_Dependency,
'build_package': Build_Package,
'build_package_dependency': Build_Package_Dependency,
'build_file': Build_File,
'variable': Variable,
'logmessage': LogMessage,
}
if model_name not in model_mapping.keys():
return HttpResponseBadRequest()
model = model_mapping[model_name]
try:
limit = int(request.GET.get('limit', 0))
except ValueError:
limit = 0
try:
offset = int(request.GET.get('offset', 0))
except ValueError:
offset = 0
ordering_string, invalid = _validate_input(request.GET.get('orderby', ''),
model)
if invalid:
return HttpResponseBadRequest()
filter_string, invalid = _validate_input(request.GET.get('filter', ''),
model)
if invalid:
return HttpResponseBadRequest()
search_term = request.GET.get('search', '')
if filter_string:
filter_terms = _get_filtering_terms(filter_string)
try:
queryset = model.objects.filter(**filter_terms)
except ValueError:
queryset = []
else:
queryset = model.objects.all()
if search_term:
queryset = _get_search_results(search_term, queryset, model)
if ordering_string and queryset:
column, order = ordering_string.split(':')
if order.lower() == DESCENDING:
queryset = queryset.order_by('-' + column)
else:
queryset = queryset.order_by(column)
if offset and limit:
queryset = queryset[offset:(offset+limit)]
elif offset:
queryset = queryset[offset:]
elif limit:
queryset = queryset[:limit]
if queryset:
response_data['count'] = queryset.count()
else:
response_data['count'] = 0
response_data['list'] = serializers.serialize('json', queryset)
return HttpResponse(json.dumps(response_data),
content_type='application/json')
def _get_filtering_terms(filter_string):
search_terms = filter_string.split(":")
keys = search_terms[0].split(',')
values = search_terms[1].split(',')
return dict(zip(keys, values))
def _validate_input(input, model):
invalid = 0
if input:
input_list = input.split(":")
# Check we have only one colon
if len(input_list) != 2:
invalid = 1
return None, invalid
# Check we have an equal number of terms both sides of the colon
if len(input_list[0].split(',')) != len(input_list[1].split(',')):
invalid = 1
return None, invalid
# Check we are looking for a valid field
valid_fields = model._meta.get_all_field_names()
for field in input_list[0].split(','):
if field not in valid_fields:
invalid = 1
return None, invalid
return input, invalid
def _get_search_results(search_term, queryset, model):
search_objects = []
for st in search_term.split(" "):
q_map = map(lambda x: Q(**{x+'__icontains': st}),
model.search_allowed_fields)
search_objects.append(reduce(operator.or_, q_map))
search_object = reduce(operator.and_, search_objects)
queryset = queryset.filter(search_object)
return queryset

10
bitbake/lib/toaster/manage.py Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "toastermain.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

View File

View File

@@ -0,0 +1,265 @@
#
# BitBake Toaster Implementation
#
# Copyright (C) 2013 Intel Corporation
#
# 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.
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
class Build(models.Model):
SUCCEEDED = 0
FAILED = 1
IN_PROGRESS = 2
BUILD_OUTCOME = (
(SUCCEEDED, 'Succeeded'),
(FAILED, 'Failed'),
(IN_PROGRESS, 'In Progress'),
)
search_allowed_fields = ['machine',
'cooker_log_path']
machine = models.CharField(max_length=100)
image_fstypes = models.CharField(max_length=100)
distro = models.CharField(max_length=100)
distro_version = models.CharField(max_length=100)
started_on = models.DateTimeField()
completed_on = models.DateTimeField()
outcome = models.IntegerField(choices=BUILD_OUTCOME, default=IN_PROGRESS)
errors_no = models.IntegerField(default=0)
warnings_no = models.IntegerField(default=0)
cooker_log_path = models.CharField(max_length=500)
build_name = models.CharField(max_length=100)
bitbake_version = models.CharField(max_length=50)
@python_2_unicode_compatible
class Target(models.Model):
search_allowed_fields = ['target', 'image_fstypes', 'file_name']
build = models.ForeignKey(Build)
target = models.CharField(max_length=100)
is_image = models.BooleanField(default = False)
file_name = models.CharField(max_length=100)
file_size = models.IntegerField()
def __str__(self):
return self.target
class Task(models.Model):
SSTATE_NA = 0
SSTATE_MISS = 1
SSTATE_FAILED = 2
SSTATE_RESTORED = 3
SSTATE_RESULT = (
(SSTATE_NA, 'Not Applicable'), # For rest of tasks, but they still need checking.
(SSTATE_MISS, 'Missing'), # it is a miss
(SSTATE_FAILED, 'Failed'), # there was a pkg, but the script failed
(SSTATE_RESTORED, 'Restored'), # succesfully restored
)
CODING_NA = 0
CODING_NOEXEC = 1
CODING_PYTHON = 2
CODING_SHELL = 3
TASK_CODING = (
(CODING_NA, 'N/A'),
(CODING_NOEXEC, 'NoExec'),
(CODING_PYTHON, 'Python'),
(CODING_SHELL, 'Shell'),
)
OUTCOME_SUCCESS = 0
OUTCOME_COVERED = 1
OUTCOME_SSTATE = 2
OUTCOME_EXISTING = 3
OUTCOME_FAILED = 4
OUTCOME_NA = 5
TASK_OUTCOME = (
(OUTCOME_SUCCESS, 'Succeeded'),
(OUTCOME_COVERED, 'Covered'),
(OUTCOME_SSTATE, 'Sstate'),
(OUTCOME_EXISTING, 'Existing'),
(OUTCOME_FAILED, 'Failed'),
(OUTCOME_NA, 'Not Available'),
)
build = models.ForeignKey(Build, related_name='task_build')
order = models.IntegerField(null=True)
task_executed = models.BooleanField(default=False) # True means Executed, False means Prebuilt
outcome = models.IntegerField(choices=TASK_OUTCOME, default=OUTCOME_NA)
sstate_checksum = models.CharField(max_length=100, blank=True)
path_to_sstate_obj = models.FilePathField(max_length=500, blank=True)
recipe = models.ForeignKey('Recipe', related_name='build_recipe')
task_name = models.CharField(max_length=100)
source_url = models.FilePathField(max_length=255, blank=True)
work_directory = models.FilePathField(max_length=255, blank=True)
script_type = models.IntegerField(choices=TASK_CODING, default=CODING_NA)
line_number = models.IntegerField(default=0)
disk_io = models.IntegerField(null=True)
cpu_usage = models.DecimalField(max_digits=6, decimal_places=2, null=True)
elapsed_time = models.CharField(max_length=50, default=0)
sstate_result = models.IntegerField(choices=SSTATE_RESULT, default=SSTATE_NA)
message = models.CharField(max_length=240)
logfile = models.FilePathField(max_length=255, blank=True)
class Meta:
ordering = ('order', 'recipe' ,)
class Task_Dependency(models.Model):
task = models.ForeignKey(Task, related_name='task_dependencies_task')
depends_on = models.ForeignKey(Task, related_name='task_dependencies_depends')
class Build_Package(models.Model):
build = models.ForeignKey('Build')
recipe = models.ForeignKey('Recipe', null=True)
name = models.CharField(max_length=100)
version = models.CharField(max_length=100, blank=True)
revision = models.CharField(max_length=32, blank=True)
summary = models.CharField(max_length=200, blank=True)
description = models.CharField(max_length=200, blank=True)
size = models.IntegerField(default=0)
section = models.CharField(max_length=80, blank=True)
license = models.CharField(max_length=80, blank=True)
class Build_Package_Dependency(models.Model):
TYPE_RDEPENDS = 0
TYPE_RPROVIDES = 1
TYPE_RRECOMMENDS = 2
TYPE_RSUGGESTS = 3
TYPE_RREPLACES = 4
TYPE_RCONFLICTS = 5
DEPENDS_TYPE = (
(TYPE_RDEPENDS, "rdepends"),
(TYPE_RPROVIDES, "rprovides"),
(TYPE_RRECOMMENDS, "rrecommends"),
(TYPE_RSUGGESTS, "rsuggests"),
(TYPE_RREPLACES, "rreplaces"),
(TYPE_RCONFLICTS, "rconflicts"),
)
package = models.ForeignKey(Build_Package, related_name='bpackage_dependencies_package')
depends_on = models.CharField(max_length=100) # soft dependency
dep_type = models.IntegerField(choices=DEPENDS_TYPE)
class Target_Package(models.Model):
target = models.ForeignKey('Target')
recipe = models.ForeignKey('Recipe', null=True)
name = models.CharField(max_length=100)
version = models.CharField(max_length=100, blank=True)
size = models.IntegerField()
class Target_Package_Dependency(models.Model):
TYPE_DEPENDS = 0
TYPE_RDEPENDS = 1
TYPE_RECOMMENDS = 2
DEPENDS_TYPE = (
(TYPE_DEPENDS, "depends"),
(TYPE_RDEPENDS, "rdepends"),
(TYPE_RECOMMENDS, "recommends"),
)
package = models.ForeignKey(Target_Package, related_name='tpackage_dependencies_package')
depends_on = models.ForeignKey(Target_Package, related_name='tpackage_dependencies_depends')
dep_type = models.IntegerField(choices=DEPENDS_TYPE)
class Build_File(models.Model):
bpackage = models.ForeignKey(Build_Package, related_name='filelist_bpackage')
path = models.FilePathField(max_length=255, blank=True)
size = models.IntegerField()
class Target_File(models.Model):
tpackage = models.ForeignKey(Target_Package, related_name='filelist_tpackage')
path = models.FilePathField(max_length=255, blank=True)
size = models.IntegerField()
class Recipe(models.Model):
name = models.CharField(max_length=100, blank=True)
version = models.CharField(max_length=100, blank=True)
layer_version = models.ForeignKey('Layer_Version', related_name='recipe_layer_version')
summary = models.CharField(max_length=100, blank=True)
description = models.CharField(max_length=100, blank=True)
section = models.CharField(max_length=100, blank=True)
license = models.CharField(max_length=200, blank=True)
licensing_info = models.TextField(blank=True)
homepage = models.URLField(blank=True)
bugtracker = models.URLField(blank=True)
file_path = models.FilePathField(max_length=255)
class Recipe_Dependency(models.Model):
TYPE_DEPENDS = 0
TYPE_RDEPENDS = 1
DEPENDS_TYPE = (
(TYPE_DEPENDS, "depends"),
(TYPE_RDEPENDS, "rdepends"),
)
recipe = models.ForeignKey(Recipe, related_name='r_dependencies_recipe')
depends_on = models.ForeignKey(Recipe, related_name='r_dependencies_depends')
dep_type = models.IntegerField(choices=DEPENDS_TYPE)
class Layer(models.Model):
name = models.CharField(max_length=100)
local_path = models.FilePathField(max_length=255)
layer_index_url = models.URLField()
class Layer_Version(models.Model):
layer = models.ForeignKey(Layer, related_name='layer_version_layer')
branch = models.CharField(max_length=50)
commit = models.CharField(max_length=100)
priority = models.IntegerField()
class Variable(models.Model):
build = models.ForeignKey(Build, related_name='variable_build')
variable_name = models.CharField(max_length=100)
variable_value = models.TextField(blank=True)
changed = models.BooleanField(default=False)
human_readable_name = models.CharField(max_length=200)
description = models.TextField(blank=True)
class VariableHistory(models.Model):
variable = models.ForeignKey(Variable)
file_name = models.FilePathField(max_length=255)
line_number = models.IntegerField(null=True)
operation = models.CharField(max_length=16)
class LogMessage(models.Model):
INFO = 0
WARNING = 1
ERROR = 2
LOG_LEVEL = ( (INFO, "info"),
(WARNING, "warn"),
(ERROR, "error") )
build = models.ForeignKey(Build)
level = models.IntegerField(choices=LOG_LEVEL, default=INFO)
message=models.CharField(max_length=240)
pathname = models.FilePathField(max_length=255, blank=True)
lineno = models.IntegerField(null=True)

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -0,0 +1,13 @@
<html>
<head>
<title>GUI Page</title>
</head>
<body>
{% load staticfiles %}
<img src="{% static "/static/images/yocto.jpg" %}" alt="Yocto"/>
This is your basic index page!
</body>
</html>

View File

@@ -0,0 +1,27 @@
#
# BitBake Toaster Implementation
#
# Copyright (C) 2013 Intel Corporation
#
# 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
from django.conf import settings
from django.conf.urls import patterns, include, url
urlpatterns = patterns('toastergui.views',
url(r'^$', 'guihome', name='guihome'),
)

View File

@@ -0,0 +1,26 @@
#
# BitBake Toaster Implementation
#
# Copyright (C) 2013 Intel Corporation
#
# 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.
from django.shortcuts import render
from orm.models import Build, Task
def guihome(request):
template = 'index.html'
return render(request, template)

View File

@@ -0,0 +1,191 @@
#
# BitBake Toaster Implementation
#
# Copyright (C) 2013 Intel Corporation
#
# 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.
# Django settings for Toaster project.
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@example.com'),
)
MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'toaster.sqlite', # Or path to database file if using sqlite3.
'USER': '',
'PASSWORD': '',
'HOST': '127.0.0.1', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
'PORT': '3306', # Set to empty string for default.
}
}
# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
ALLOWED_HOSTS = []
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
# Always use local computer's time zone
import time
TIME_ZONE = time.tzname[0]
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True
# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/var/www/example.com/media/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://example.com/media/", "http://media.example.com/"
MEDIA_URL = ''
# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/var/www/example.com/static/"
STATIC_ROOT = ''
# URL prefix for static files.
# Example: "http://example.com/static/", "http://static.example.com/"
STATIC_URL = '/static/'
# Additional locations of static files
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# Make this unique, and don't share it with anybody.
SECRET_KEY = 'NOT_SUITABLE_FOR_HOSTED_DEPLOYMENT'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
# Uncomment the next line for simple clickjacking protection:
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'toastermain.urls'
# Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = 'toastermain.wsgi.application'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
INSTALLED_APPS = (
#'django.contrib.auth',
#'django.contrib.contenttypes',
#'django.contrib.sessions',
#'django.contrib.sites',
#'django.contrib.messages',
'django.contrib.staticfiles',
# Uncomment the next line to enable the admin:
# 'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
'orm',
'toastermain',
'bldviewer',
'toastergui',
)
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error when DEBUG=False.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
# If we're using sqlite, we need to tweak the performance a bit
from django.db.backends.signals import connection_created
def activate_synchronous_off(sender, connection, **kwargs):
if connection.vendor == 'sqlite':
cursor = connection.cursor()
cursor.execute('PRAGMA synchronous = 0;')
connection_created.connect(activate_synchronous_off)
#

View File

@@ -0,0 +1,41 @@
#
# BitBake Toaster Implementation
#
# Copyright (C) 2013 Intel Corporation
#
# 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.
from django.conf.urls import patterns, include, url
from django.views.generic.simple import redirect_to
from django.views.decorators.cache import never_cache
# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
# admin.autodiscover()
urlpatterns = patterns('',
url(r'^simple/', include('bldviewer.urls')),
url(r'^api/1.0/', include('bldviewer.api')),
url(r'^gui/', include('toastergui.urls')),
url(r'^$', never_cache(redirect_to), {'url': '/simple/'}),
# Examples:
# url(r'^toaster/', include('toaster.foo.urls')),
# Uncomment the admin/doc line below to enable admin documentation:
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
# url(r'^admin/', include(admin.site.urls)),
)

View File

@@ -0,0 +1,32 @@
"""
WSGI config for Toaster project.
This module contains the WSGI application used by Django's development server
and any production WSGI deployments. It should expose a module-level variable
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
this application via the ``WSGI_APPLICATION`` setting.
Usually you will have the standard Django WSGI application here, but it also
might make sense to replace the whole Django WSGI application with a custom one
that later delegates to the Django one. For example, you could introduce WSGI
middleware here, or combine a Django application with an application of another
framework.
"""
import os
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
# if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use
# os.environ["DJANGO_SETTINGS_MODULE"] = "Toaster.settings"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "toastermain.settings")
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
# Apply WSGI middleware here.
# from helloworld.wsgi import HelloWorldApplication
# application = HelloWorldApplication(application)

View File

@@ -0,0 +1,110 @@
#
# Toaster helper class
#
# Copyright (C) 2013 Intel Corporation
#
# Released under the MIT license (see COPYING.MIT)
#
# This bbclass is designed to extract data used by OE-Core during the build process,
# for recording in the Toaster system.
# The data access is synchronous, preserving the build data integrity across
# different builds.
#
# The data is transferred through the event system, using the MetadataEvent objects.
#
# The model is to enable the datadump functions as postfuncs, and have the dump
# executed after the real taskfunc has been executed. This prevents task signature changing
# is toaster is enabled or not. Build performance is not affected if Toaster is not enabled.
#
# To enable, use INHERIT in local.conf:
#
# INHERIT += "toaster"
#
#
#
#
# 1. Dump package file info data
python toaster_package_dumpdata() {
"""
Dumps the data created by emit_pkgdata
"""
# replicate variables from the package.bbclass
packages = d.getVar('PACKAGES', True)
pkgdest = d.getVar('PKGDEST', True)
pkgdatadir = d.getVar('PKGDESTWORK', True)
# scan and send data for each package
import ast
import fnmatch
lpkgdata = {}
for pkg in packages.split():
subdata_file = pkgdatadir + "/runtime/%s" % pkg
lpkgdata = {}
sf = open(subdata_file, "r")
line = sf.readline()
while line:
(n, v) = line.rstrip().split(":", 1)
if pkg in n:
n = n.replace("_" + pkg, "")
lpkgdata[n] = v.strip()
line = sf.readline()
pkgsplitname = os.path.join(pkgdest, pkg)
# replace FILES_INFO data with a dictionary of file name - file size
if n == 'FILES_INFO':
filesizedata = {}
val = v.strip().replace('\\\'', '\'')
dictval = ast.literal_eval(val)
for parent, dirlist in dictval.items():
idx = parent.find(pkgsplitname)
if idx > -1:
parent = parent[idx+len(pkgsplitname):]
else:
bb.error("Invalid path while looking for file ", parent)
for basename in dirlist:
fullpath = os.path.join(parent, basename)
try:
filesizedata[fullpath] = os.stat(pkgsplitname + fullpath).st_size
except OSError:
# we may hit a symlink that is not pointing correctly over package-split
filesizedata[fullpath] = 0
lpkgdata[n] = filesizedata
# Fire an event containing the pkg data
bb.event.fire(bb.event.MetadataEvent("SinglePackageInfo", lpkgdata), d)
}
# 2. Dump output image files information
python toaster_image_dumpdata() {
"""
Image filename for output images is not standardized.
image_types.bbclass will spell out IMAGE_CMD_xxx variables that actually
have hardcoded ways to create image file names in them.
So we look for files starting with the set name.
"""
deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE', True);
image_name = d.getVar('IMAGE_NAME', True);
image_info_data = {}
for dirpath, dirnames, filenames in os.walk(deploy_dir_image):
for fn in filenames:
if fn.startswith(image_name):
image_info_data[dirpath + fn] = os.stat(os.path.join(dirpath, fn)).st_size
bb.event.fire(bb.event.MetadataEvent("ImageFileSize",image_info_data), d)
}
do_package[postfuncs] += "toaster_package_dumpdata "
do_rootfs[postfuncs] += "toaster_image_dumpdata "

View File

@@ -42,6 +42,7 @@ CFLAGS[doc] = "Flags passed to the C compiler for the target system. This variab
COMBINED_FEATURES[doc] = "A set of features common between MACHINE_FEATURES and DISTRO_FEATURES."
COMPATIBLE_HOST[doc] = "A regular expression that resolves to one or more hosts (when the recipe is native) or one or more targets (when the recipe is non-native) with which a recipe is compatible."
COMPATIBLE_MACHINE[doc] = "A regular expression that resolves to one or more target machines with which a recipe is compatible."
CONF_VERSION[doc] = "Increased each time build/conf/ changes incompatibly and used to track the version of local.conf"
CONFFILES[doc] = "Identifies editable or configurable files that are part of a package."
CONFIG_SITE[doc] = "A list of files that contains autoconf test results relevant to the current build. This variable is used by the Autotools utilities when running configure."
CORE_IMAGE_EXTRA_INSTALL[doc] = "Specifies the list of packages to be added to the image. You should only set this variable in the conf/local.conf file in the Build Directory."
@@ -117,6 +118,7 @@ IMAGE_LINGUAS[doc] = "Specifies the list of locales to install into the image du
IMAGE_OVERHEAD_FACTOR[doc] = "Defines a multiplier that the build system applies to the initial image size for cases when the multiplier times the returned disk usage value for the image is greater than the sum of IMAGE_ROOTFS_SIZE and IMAGE_ROOTFS_EXTRA_SPACE."
IMAGE_ROOTFS_EXTRA_SPACE[doc] = "Defines additional free disk space created in the image in Kbytes. By default, this variable is set to '0'."
IMAGE_ROOTFS_SIZE[doc] = "Defines the size in Kbytes for the generated image."
IMAGETEST[doc] = "Enable test booting of virtual machine images under the qemu emulator after any root filesystems are created and run tests against those images."
INC_PR[doc] = "Helps define the recipe revision for recipes that share a common include file."
INHIBIT_PACKAGE_STRIP[doc] = "If set to "1", causes the build to not strip binaries in resulting packages."
INHERIT[doc] = "Causes the named class to be inherited at this point during parsing. The variable is only valid in configuration files."
@@ -170,18 +172,10 @@ MLPREFIX[doc] = "Specifies a prefix has been added to PN to create a special ver
MODULE_TARBALL_DEPLOY[doc] = "Controls creation of the modules-*.tgz file. Set this variable to "0" to disable creation of this file, which contains all of the kernel modules resulting from a kernel build."
MULTIMACH_TARGET_SYS[doc] = "Separates files for different machines such that you can build for multiple target machines using the same output directories."
BAD_RECOMMENDATIONS[doc] = "List of packages to not install if recommended. These will still be installed if required."
NO_RECOMMENDATIONS[doc] = "When set to 1, no recommended packages will be installed. Note: some recommended packages may be required for certain system functionality, such as kernel-modules. It is up to the user to add packages to IMAGE_INSTALL as needed."
IMAGE_INSTALL[doc] = "Used by an image recipe to list the packages to be installed. See PACKAGE_INSTALL."
PACKAGE_EXCLUDE[doc] = "Packages to exclude from the installation, if required an error will be generated."
PACKAGE_INSTALL[doc] = "Generally not user defined. List of the packages to be installed into the image, uses IMAGE_INSTALL as part of the list."
PACKAGE_INSTALL_ATTEMPTONLY[doc] = "Generally not user defined. List of packages that will be attempted to be installed, but no error will generate if any of them fail to install."
#N
NATIVELSBSTRING[doc] = "A string identifying the host distribution."
NO_RECOMMENDATIONS[doc] = "When set to 1, no recommended packages will be installed. Note: some recommended packages may be required for certain system functionality, such as kernel-modules. It is up to the user to add packages to IMAGE_INSTALL as needed."
#O
@@ -194,12 +188,16 @@ PACKAGE_ARCH[doc] = "The architecture of the resulting package or packages."
PACKAGE_ARCHS[doc] = "A list of architectures compatible with the given target in order of priority."
PACKAGE_BEFORE_PN[doc] = "Enables easily adding packages to PACKAGES before ${PN} so that the packages can pick up files that would normally be included in the default package."
PACKAGE_CLASSES[doc] = "This variable specifies the package manager to use when packaging data. It is set in the conf/local.conf file in the Build Directory."
PACKAGE_EXCLUDE[doc] = "Packages to exclude from the installation, if required an error will be generated."
PACKAGE_EXTRA_ARCHS[doc] = "Specifies the list of architectures compatible with the device CPU. This variable is useful when you build for several different devices that use miscellaneous processors."
PACKAGE_INSTALL[doc] = "Generally not user defined. List of the packages to be installed into the image, uses IMAGE_INSTALL as part of the list."
PACKAGE_INSTALL_ATTEMPTONLY[doc] = "Generally not user defined. List of packages that will be attempted to be installed, but no error will generate if any of them fail to install."
PACKAGECONFIG[doc] = "This variable provides a means of enabling or disabling features of a recipe on a per-recipe basis."
PACKAGES[doc] = "The list of packages to be created from the recipe."
PACKAGES_DYNAMIC[doc] = "A promise that your recipe satisfies runtime dependencies for optional modules that are found in other recipes."
PALMTOP_USE_MULTITHREADED_QT[doc] = "Set to yes, if you want to build qt apps with CONFIG+=thread"
PARALLEL_MAKE[doc] = "Specifies extra options that are passed to the make command during the compile tasks. This variable is usually in the form -j 4, where the number represents the maximum number of parallel threads make can run."
PATCHRESOLVE[doc] = "Enable / disable interactive patch resolution."
PE[doc] = "The epoch of the recipe. The default value is '0'. The field is used to make upgrades possible when the versioning scheme changes in some backwards incompatible way."
PF[doc] = "Specifies the recipe or package name and includes all version and revision numbers. This variable is comprised of ${PN}-${EXTENDPE}${PV}-${PR}"
PN[doc] = "PN refers to a recipe name in the context of a file used by the OpenEmbedded build system as input to create a package. It refers to a package name in the context of a file created or produced by the OpenEmbedded build system."
@@ -227,6 +225,7 @@ RSUGGESTS[doc] = "A list of additional packages that you can suggest for install
S[doc] = "The location in the Build Directory where unpacked package source code resides."
SANITY_TESTED_DISTROS[doc] = "A list of the host distribution identifiers that the build system has been tested against."
SDKIMAGE_FEATURES[doc] = "Equivalent to IMAGE_FEATURES. However, this variable applies to the SDK generated from an image using the command '$ bitbake -c populate_sdk imagename'."
SDKMACHINE[doc] = "Specifies the architecture (i686 or x86_64) to build SDK/ADT tiems for."
SECTION[doc] = "The section in which packages should be categorized. Package management utilities can make use of this variable."
SELECTED_OPTIMIZATION[doc] = "The variable takes the value of FULL_OPTIMIZATION unless DEBUG_BUILD = "1". In this case the value of DEBUG_OPTIMIZATION is used."
SERIAL_CONSOLE[doc] = "The speed and device for the serial port used to attach the serial console. This variable is given to the kernel as the 'console' parameter and after booting occurs getty is started on that port so remote login is possible."
@@ -263,6 +262,8 @@ TARGET_PREFIX[doc] = "The prefix for the cross compile toolchain. E.g arm-linux-
TARGET_SYS[doc] = "The target system is composed out of TARGET_ARCH,TARGET_VENDOR and TARGET_OS."
TCLIBC[doc] = "Specifies which variant of the GNU standard C library (libc) to use during the build process. You can select eglibc or uclibc."
TCMODE[doc] = "The toolchain selector. It selects the external toolchain built using the OpenEmbedded build system or a few supported combinations of the upstream GCC or CodeSourcery Labs toolchain."
TEST_SCEN[doc] = "Controls which tests are run against virtual images if testing is enabled with IMAGETEST."
TEST_SERIALIZE[doc] = "Controls the time taken by the sanity tests."
TIME[doc] = "The time the build was started HMS"
TMPDIR[doc] = "This variable is the temporary directory the OpenEmbedded build system uses when it does its work building images. By default, the TMPDIR variable is named tmp within the Build Directory."
TOPDIR[doc] = "This variable is the Build Directory. BitBake automatically sets this variable. The OpenEmbedded build system uses the Build Directory when building images."
@@ -272,6 +273,10 @@ TUNEABI_WHITELIST[doc] = "A whitelist of permissible TUNEABI values; if unset, a
TUNECONFLICTS[doc] = "List of conflicting features for a given feature."
TUNEVALID[doc] = "Descriptions of valid tuning features, stored as flags."
#T
USER_CLASSES[doc] = "List of additional classes to use when building images which enable extra features."
#W
WORKDIR[doc] = "The pathname of the working directory in which the OpenEmbedded build system builds a recipe. This directory is located within the TMPDIR directory structure and changes as different packages are built."