summaryrefslogtreecommitdiff
path: root/yocto-poky/bitbake/lib/toaster/toastergui/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'yocto-poky/bitbake/lib/toaster/toastergui/views.py')
-rwxr-xr-xyocto-poky/bitbake/lib/toaster/toastergui/views.py1200
1 files changed, 515 insertions, 685 deletions
diff --git a/yocto-poky/bitbake/lib/toaster/toastergui/views.py b/yocto-poky/bitbake/lib/toaster/toastergui/views.py
index 0e255f1b8..bd5bf6334 100755
--- a/yocto-poky/bitbake/lib/toaster/toastergui/views.py
+++ b/yocto-poky/bitbake/lib/toaster/toastergui/views.py
@@ -27,15 +27,15 @@ import operator,re
from django.db.models import F, Q, Sum, Count, Max
from django.db import IntegrityError, Error
-from django.shortcuts import render, redirect
+from django.shortcuts import render, redirect, get_object_or_404
from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable
from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency
-from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact
+from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact, CustomImagePackage
from orm.models import BitbakeVersion, CustomImageRecipe
from bldcontrol import bbcontroller
from django.views.decorators.cache import cache_control
from django.core.urlresolvers import reverse, resolve
-from django.core.exceptions import MultipleObjectsReturned
+from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.http import HttpResponseBadRequest, HttpResponseNotFound
from django.utils import timezone
@@ -43,7 +43,9 @@ from django.utils.html import escape
from datetime import timedelta, datetime
from django.utils import formats
from toastergui.templatetags.projecttags import json as jsonfilter
+from decimal import Decimal
import json
+import os
from os.path import dirname
from functools import wraps
import itertools
@@ -73,13 +75,9 @@ class MimeTypeFinder(object):
def landing(request):
# in build mode, we redirect to the command-line builds page
# if there are any builds for the default (cli builds) project
- default_project = Project.objects.get_default_project()
+ default_project = Project.objects.get_or_create_default_project()
default_project_builds = Build.objects.filter(project = default_project)
- if (not toastermain.settings.BUILD_MODE) and default_project_builds.count() > 0:
- args = (default_project.id,)
- return redirect(reverse('projectbuilds', args = args), permanent = False)
-
# we only redirect to projects page if there is a user-generated project
num_builds = Build.objects.all().count()
user_projects = Project.objects.filter(is_default = False)
@@ -95,52 +93,6 @@ def landing(request):
return render(request, 'landing.html', context)
-# returns a list for most recent builds;
-def _get_latest_builds(prj=None):
- queryset = Build.objects.all()
-
- if prj is not None:
- queryset = queryset.filter(project = prj)
-
- if not toastermain.settings.BUILD_MODE:
- queryset = queryset.exclude(project__is_default=False)
-
- return list(itertools.chain(
- queryset.filter(outcome=Build.IN_PROGRESS).order_by("-started_on"),
- queryset.filter(outcome__lt=Build.IN_PROGRESS).order_by("-started_on")[:3] ))
-
-
-# a JSON-able dict of recent builds; for use in the Project page, xhr_ updates, and other places, as needed
-def _project_recent_build_list(prj):
- data = []
- # take the most recent 3 completed builds, plus any builds in progress
- for x in _get_latest_builds(prj):
- d = {
- "id": x.pk,
- "targets" : map(lambda y: {"target": y.target, "task": y.task }, x.target_set.all()), # TODO: create the task entry in the Target table
- "status": x.get_current_status(),
- "errors": map(lambda y: {"type": y.lineno, "msg": y.message, "tb": y.pathname}, (x.logmessage_set.filter(level__gte=LogMessage.WARNING)|x.logmessage_set.filter(level=LogMessage.EXCEPTION))),
- "updated": x.completed_on.strftime('%s')+"000",
- "command_time": (x.completed_on - x.started_on).total_seconds(),
- "br_page_url": reverse('builddashboard', args=(x.pk,) ),
- "build" : map( lambda y: {"id": y.pk,
- "status": y.get_outcome_display(),
- "completed_on" : y.completed_on.strftime('%s')+"000",
- "build_time" : (y.completed_on - y.started_on).total_seconds(),
- "build_page_url" : reverse('builddashboard', args=(y.pk,)),
- 'build_time_page_url': reverse('buildtime', args=(y.pk,)),
- "errors": y.errors.count(),
- "warnings": y.warnings.count(),
- "completeper": y.completeper() if y.outcome == Build.IN_PROGRESS else "0",
- "eta": y.eta().strftime('%s')+"000" if y.outcome == Build.IN_PROGRESS else "0",
- }, [x]),
- }
- data.append(d)
-
- return data
-
-
-
def objtojson(obj):
from django.db.models.query import QuerySet
from django.db.models import Model
@@ -151,6 +103,8 @@ def objtojson(obj):
return obj.total_seconds()
elif isinstance(obj, QuerySet) or isinstance(obj, set):
return list(obj)
+ elif isinstance(obj, Decimal):
+ return str(obj)
elif type(obj).__name__ == "RelatedManager":
return [x.pk for x in obj.all()]
elif hasattr( obj, '__dict__') and isinstance(obj, Model):
@@ -509,7 +463,7 @@ def builddashboard( request, build_id ):
for t in tgts:
elem = { }
elem[ 'target' ] = t
- if ( t.is_image ):
+ if t.is_image:
hasImages = True
npkg = 0
pkgsz = 0
@@ -527,9 +481,13 @@ def builddashboard( request, build_id ):
if ( ndx < 0 ):
ndx = 0;
f = i.file_name[ ndx + 1: ]
- imageFiles.append({ 'id': i.id, 'path': f, 'size' : i.file_size })
- if ( t.is_image and
- (( len( imageFiles ) <= 0 ) or ( len( t.license_manifest_path ) <= 0 ))):
+ imageFiles.append({
+ 'id': i.id,
+ 'path': f,
+ 'size': i.file_size,
+ 'suffix': i.suffix
+ })
+ if t.is_image and (len(imageFiles) <= 0 or len(t.license_manifest_path) <= 0):
targetHasNoImages = True
elem[ 'imageFiles' ] = imageFiles
elem[ 'targetHasNoImages' ] = targetHasNoImages
@@ -549,6 +507,7 @@ def builddashboard( request, build_id ):
context = {
'build' : build,
+ 'project' : build.project,
'hasImages' : hasImages,
'ntargets' : ntargets,
'targets' : targets,
@@ -705,7 +664,9 @@ def recipe_packages(request, build_id, recipe_id):
def target_common( request, build_id, target_id, variant ):
template = "target.html"
- (pagesize, orderby) = _get_parameters_values(request, 25, 'name:+')
+ default_orderby = 'name:+'
+
+ (pagesize, orderby) = _get_parameters_values(request, 25, default_orderby)
mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
retval = _verify_parameters( request.GET, mandatory_parameters )
if retval:
@@ -725,8 +686,6 @@ def target_common( request, build_id, target_id, variant ):
queryset = queryset.select_related("recipe", "recipe__layer_version", "recipe__layer_version__layer")
packages = _build_page_range( Paginator(queryset, pagesize), request.GET.get( 'page', 1 ))
-
-
build = Build.objects.get( pk = build_id )
# bring in package dependencies
@@ -839,11 +798,12 @@ eans multiple licenses exist that cover different parts of the source',
context = {
'objectname': variant,
'build' : build,
+ 'project' : build.project,
'target' : Target.objects.filter( pk = target_id )[ 0 ],
'objects' : packages,
'packages_sum' : packages_sum[ 'installed_size__sum' ],
'object_search_display': "packages included",
- 'default_orderby' : orderby,
+ 'default_orderby' : default_orderby,
'tablecols' : [
tc_package,
tc_packageVersion,
@@ -979,7 +939,10 @@ def dirinfo(request, build_id, target_id, file_path=None):
if head != sep:
dir_list.insert(0, head)
- context = { 'build': Build.objects.get(pk=build_id),
+ build = Build.objects.get(pk=build_id)
+
+ context = { 'build': build,
+ 'project': build.project,
'target': Target.objects.get(pk=target_id),
'packages_sum': packages_sum['installed_size__sum'],
'objects': objects,
@@ -1038,29 +1001,29 @@ def tasks_common(request, build_id, variant, task_anchor):
anchor=task_anchor
# default ordering depends on variant
- if 'buildtime' == variant:
- title_variant='Time'
- object_search_display="time data"
- filter_search_display="tasks"
- (pagesize, orderby) = _get_parameters_values(request, 25, 'elapsed_time:-')
- elif 'diskio' == variant:
- title_variant='Disk I/O'
- object_search_display="disk I/O data"
- filter_search_display="tasks"
- (pagesize, orderby) = _get_parameters_values(request, 25, 'disk_io:-')
- elif 'cpuusage' == variant:
- title_variant='CPU usage'
- object_search_display="CPU usage data"
- filter_search_display="tasks"
- (pagesize, orderby) = _get_parameters_values(request, 25, 'cpu_usage:-')
- else :
- title_variant='Tasks'
- object_search_display="tasks"
- filter_search_display="tasks"
- (pagesize, orderby) = _get_parameters_values(request, 25, 'order:+')
+ default_orderby = None
+ filter_search_display = 'tasks'
+
+ if 'buildtime' == variant:
+ default_orderby = 'elapsed_time:-'
+ title_variant = 'Time'
+ object_search_display = 'time data'
+ elif 'diskio' == variant:
+ default_orderby = 'disk_io:-'
+ title_variant = 'Disk I/O'
+ object_search_display = 'disk I/O data'
+ elif 'cputime' == variant:
+ default_orderby = 'cpu_time_system:-'
+ title_variant='CPU time'
+ object_search_display = 'CPU time data'
+ else:
+ default_orderby = 'order:+'
+ title_variant = 'Tasks'
+ object_search_display = 'tasks'
+ (pagesize, orderby) = _get_parameters_values(request, 25, default_orderby)
- mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
+ mandatory_parameters = {'count': pagesize, 'page' : 1, 'orderby': orderby}
template = 'tasks.html'
retval = _verify_parameters( request.GET, mandatory_parameters )
@@ -1204,23 +1167,38 @@ def tasks_common(request, build_id, variant, task_anchor):
del tc_time['clclass']
tc_cache['hidden']='1'
- tc_cpu={
- 'name':'CPU usage',
- 'qhelp':'The percentage of task CPU utilization',
- 'orderfield': _get_toggle_order(request, "cpu_usage", True),
- 'ordericon':_get_toggle_order_icon(request, "cpu_usage"),
- 'orderkey' : 'cpu_usage',
- 'clclass': 'cpu_used', 'hidden' : 1,
+ tc_cpu_time_system={
+ 'name':'System CPU time (secs)',
+ 'qhelp':'Total amount of time spent executing in kernel mode, in ' +
+ 'seconds. Note that this time can be greater than the task ' +
+ 'time due to parallel execution.',
+ 'orderfield': _get_toggle_order(request, "cpu_time_system", True),
+ 'ordericon':_get_toggle_order_icon(request, "cpu_time_system"),
+ 'orderkey' : 'cpu_time_system',
+ 'clclass': 'cpu_time_system', 'hidden' : 1,
}
- if 'cpuusage' == variant:
- tc_cpu['hidden']='0'
- del tc_cpu['clclass']
+ tc_cpu_time_user={
+ 'name':'User CPU time (secs)',
+ 'qhelp':'Total amount of time spent executing in user mode, in seconds. ' +
+ 'Note that this time can be greater than the task time due to ' +
+ 'parallel execution.',
+ 'orderfield': _get_toggle_order(request, "cpu_time_user", True),
+ 'ordericon':_get_toggle_order_icon(request, "cpu_time_user"),
+ 'orderkey' : 'cpu_time_user',
+ 'clclass': 'cpu_time_user', 'hidden' : 1,
+ }
+
+ if 'cputime' == variant:
+ tc_cpu_time_system['hidden']='0'
+ tc_cpu_time_user['hidden']='0'
+ del tc_cpu_time_system['clclass']
+ del tc_cpu_time_user['clclass']
tc_cache['hidden']='1'
tc_diskio={
- 'name':'Disk I/O (ms)',
- 'qhelp':'Number of miliseconds the task spent doing disk input and output',
+ 'name':'Disk I/O (bytes)',
+ 'qhelp':'Number of bytes written to and read from the disk during the task',
'orderfield': _get_toggle_order(request, "disk_io", True),
'ordericon':_get_toggle_order_icon(request, "disk_io"),
'orderkey' : 'disk_io',
@@ -1238,8 +1216,9 @@ def tasks_common(request, build_id, variant, task_anchor):
'filter_search_display': filter_search_display,
'mainheading': title_variant,
'build': build,
+ 'project': build.project,
'objects': task_objects,
- 'default_orderby' : orderby,
+ 'default_orderby' : default_orderby,
'search_term': search_term,
'total_count': queryset_with_search.count(),
'tablecols':[
@@ -1251,7 +1230,8 @@ def tasks_common(request, build_id, variant, task_anchor):
tc_outcome,
tc_cache,
tc_time,
- tc_cpu,
+ tc_cpu_time_system,
+ tc_cpu_time_user,
tc_diskio,
]}
@@ -1272,9 +1252,8 @@ def buildtime(request, build_id):
def diskio(request, build_id):
return tasks_common(request, build_id, 'diskio', '')
-def cpuusage(request, build_id):
- return tasks_common(request, build_id, 'cpuusage', '')
-
+def cputime(request, build_id):
+ return tasks_common(request, build_id, 'cputime', '')
def recipes(request, build_id):
template = 'recipes.html'
@@ -1284,7 +1263,10 @@ def recipes(request, build_id):
if retval:
return _redirect_parameters( 'recipes', request.GET, mandatory_parameters, build_id = build_id)
(filter_string, search_term, ordering_string) = _search_tuple(request, Recipe)
- queryset = Recipe.objects.filter(layer_version__id__in=Layer_Version.objects.filter(build=build_id)).select_related("layer_version", "layer_version__layer")
+
+ build = Build.objects.get(pk=build_id)
+
+ queryset = build.get_recipes()
queryset = _get_queryset(Recipe, queryset, filter_string, search_term, ordering_string, 'name')
recipes = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
@@ -1303,11 +1285,10 @@ def recipes(request, build_id):
revlist.append(recipe_dep)
revs[recipe.id] = revlist
- build = Build.objects.get(pk=build_id)
-
context = {
'objectname': 'recipes',
'build': build,
+ 'project': build.project,
'objects': recipes,
'default_orderby' : 'name:+',
'recipe_deps' : deps,
@@ -1392,10 +1373,12 @@ def configuration(request, build_id):
'MACHINE', 'DISTRO', 'DISTRO_VERSION', 'TUNE_FEATURES', 'TARGET_FPU')
context = dict(Variable.objects.filter(build=build_id, variable_name__in=var_names)\
.values_list('variable_name', 'variable_value'))
+ build = Build.objects.get(pk=build_id)
context.update({'objectname': 'configuration',
'object_search_display':'variables',
'filter_search_display':'variables',
- 'build': Build.objects.get(pk=build_id),
+ 'build': build,
+ 'project': build.project,
'targets': Target.objects.filter(build=build_id)})
return render(request, template, context)
@@ -1432,12 +1415,15 @@ def configvars(request, build_id):
file_filter += '/bitbake.conf'
build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path)
+ build = Build.objects.get(pk=build_id)
+
context = {
'objectname': 'configvars',
'object_search_display':'BitBake variables',
'filter_search_display':'variables',
'file_filter': file_filter,
- 'build': Build.objects.get(pk=build_id),
+ 'build': build,
+ 'project': build.project,
'objects' : variables,
'total_count':queryset_with_search.count(),
'default_orderby' : 'variable_name:+',
@@ -1506,6 +1492,7 @@ def bpackage(request, build_id):
context = {
'objectname': 'packages built',
'build': build,
+ 'project': build.project,
'objects' : packages,
'default_orderby' : 'name:+',
'tablecols':[
@@ -1580,7 +1567,12 @@ def bpackage(request, build_id):
def bfile(request, build_id, package_id):
template = 'bfile.html'
files = Package_File.objects.filter(package = package_id)
- context = {'build': Build.objects.get(pk=build_id), 'objects' : files}
+ build = Build.objects.get(pk=build_id)
+ context = {
+ 'build': build,
+ 'project': build.project,
+ 'objects' : files
+ }
return render(request, template, context)
@@ -1893,11 +1885,6 @@ def managedcontextprocessor(request):
"projects": projects,
"non_cli_projects": projects.exclude(is_default=True),
"DEBUG" : toastermain.settings.DEBUG,
-
- # True if Toaster is in build mode, False otherwise
- "BUILD_MODE": toastermain.settings.BUILD_MODE,
-
- "CUSTOM_IMAGE" : toastermain.settings.CUSTOM_IMAGE,
"TOASTER_BRANCH": toastermain.settings.TOASTER_BRANCH,
"TOASTER_REVISION" : toastermain.settings.TOASTER_REVISION,
}
@@ -1908,6 +1895,7 @@ def managedcontextprocessor(request):
import toastermain.settings
from orm.models import Project, ProjectLayer, ProjectTarget, ProjectVariable
+from bldcontrol.models import BuildEnvironment
# we have a set of functions if we're in managed mode, or
# a default "page not available" simple functions for interactive mode
@@ -1926,263 +1914,6 @@ if True:
''' The exception raised on invalid POST requests '''
pass
- # shows the "all builds" page for managed mode; it displays build requests (at least started!) instead of actual builds
- # WARNING _build_list_helper() may raise a RedirectException, which
- # will set the GET parameters and redirect back to the
- # all-builds or projectbuilds page as appropriate;
- # TODO don't use exceptions to control program flow
- @_template_renderer("builds.html")
- def builds(request):
- # define here what parameters the view needs in the GET portion in order to
- # be able to display something. 'count' and 'page' are mandatory for all views
- # that use paginators.
-
- queryset = Build.objects.all()
-
- # if in analysis mode, exclude builds for all projects except
- # command line builds
- if not toastermain.settings.BUILD_MODE:
- queryset = queryset.exclude(project__is_default=False)
-
- redirect_page = resolve(request.path_info).url_name
-
- context, pagesize, orderby = _build_list_helper(request,
- queryset,
- redirect_page)
- # all builds page as a Project column
- context['tablecols'].append({
- 'name': 'Project',
- 'clclass': 'project_column'
- })
-
- _set_parameters_values(pagesize, orderby, request)
- return context
-
-
- # helper function, to be used on "all builds" and "project builds" pages
- def _build_list_helper(request, queryset_all, redirect_page, pid=None):
- default_orderby = 'completed_on:-'
- (pagesize, orderby) = _get_parameters_values(request, 10, default_orderby)
- mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
- retval = _verify_parameters( request.GET, mandatory_parameters )
- if retval:
- params = {}
- if pid:
- params = {'pid': pid}
- raise RedirectException(redirect_page,
- request.GET,
- mandatory_parameters,
- **params)
-
- # boilerplate code that takes a request for an object type and returns a queryset
- # for that object type. copypasta for all needed table searches
- (filter_string, search_term, ordering_string) = _search_tuple(request, Build)
-
- # post-process any date range filters
- filter_string, daterange_selected = _modify_date_range_filter(filter_string)
-
- # don't show "in progress" builds in "all builds" or "project builds"
- queryset_all = queryset_all.exclude(outcome = Build.IN_PROGRESS)
-
- # append project info
- queryset_all = queryset_all.select_related("project")
-
- # annotate with number of ERROR and EXCEPTION log messages
- queryset_all = queryset_all.annotate(
- errors_no = Count(
- 'logmessage',
- only=Q(logmessage__level=LogMessage.ERROR) |
- Q(logmessage__level=LogMessage.EXCEPTION)
- )
- )
-
- # annotate with number of warnings
- q_warnings = Q(logmessage__level=LogMessage.WARNING)
- queryset_all = queryset_all.annotate(
- warnings_no = Count('logmessage', only=q_warnings)
- )
-
- # add timespent field
- timespent = 'completed_on - started_on'
- queryset_all = queryset_all.extra(select={'timespent': timespent})
-
- queryset_with_search = _get_queryset(Build, queryset_all,
- None, search_term,
- ordering_string, '-completed_on')
-
- queryset = _get_queryset(Build, queryset_all,
- filter_string, search_term,
- ordering_string, '-completed_on')
-
- # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display
- build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
-
- # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
- build_mru = _get_latest_builds()[:3]
-
- # calculate the exact begining of local today and yesterday, append context
- context_date,today_begin,yesterday_begin = _add_daterange_context(queryset_all, request, {'started_on','completed_on'})
-
- # set up list of fstypes for each build
- fstypes_map = {};
- for build in build_info:
- targets = Target.objects.filter( build_id = build.id )
- comma = "";
- extensions = "";
- for t in targets:
- if ( not t.is_image ):
- continue
- tif = Target_Image_File.objects.filter( target_id = t.id )
- for i in tif:
- s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
- if s == i.file_name:
- s=re.sub('.*\.', '', i.file_name)
- if None == re.search(s,extensions):
- extensions += comma + s
- comma = ", "
- fstypes_map[build.id]=extensions
-
- # send the data to the template
- context = {
- # specific info for
- 'mru' : build_mru,
- # TODO: common objects for all table views, adapt as needed
- 'objects' : build_info,
- 'objectname' : "builds",
- 'default_orderby' : default_orderby,
- 'fstypes' : fstypes_map,
- 'search_term' : search_term,
- 'total_count' : queryset_with_search.count(),
- 'daterange_selected' : daterange_selected,
- # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
- 'tablecols' : [
- {'name': 'Outcome', # column with a single filter
- 'qhelp' : "The outcome tells you if a build successfully completed or failed", # the help button content
- 'dclass' : "span2", # indication about column width; comes from the design
- 'orderfield': _get_toggle_order(request, "outcome"), # adds ordering by the field value; default ascending unless clicked from ascending into descending
- 'ordericon':_get_toggle_order_icon(request, "outcome"),
- # filter field will set a filter on that column with the specs in the filter description
- # the class field in the filter has no relation with clclass; the control different aspects of the UI
- # still, it is recommended for the values to be identical for easy tracking in the generated HTML
- 'filter' : {'class' : 'outcome',
- 'label': 'Show:',
- 'options' : [
- ('Successful builds', 'outcome:' + str(Build.SUCCEEDED), queryset_with_search.filter(outcome=str(Build.SUCCEEDED)).count()), # this is the field search expression
- ('Failed builds', 'outcome:'+ str(Build.FAILED), queryset_with_search.filter(outcome=str(Build.FAILED)).count()),
- ]
- }
- },
- {'name': 'Recipe', # default column, disabled box, with just the name in the list
- 'qhelp': "What you built (i.e. one or more recipes or image recipes)",
- 'orderfield': _get_toggle_order(request, "target__target"),
- 'ordericon':_get_toggle_order_icon(request, "target__target"),
- },
- {'name': 'Machine',
- 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe",
- 'orderfield': _get_toggle_order(request, "machine"),
- 'ordericon':_get_toggle_order_icon(request, "machine"),
- 'dclass': 'span3'
- }, # a slightly wider column
- {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column
- 'qhelp': "The date and time you started the build",
- 'orderfield': _get_toggle_order(request, "started_on", True),
- 'ordericon':_get_toggle_order_icon(request, "started_on"),
- 'orderkey' : "started_on",
- 'filter' : {'class' : 'started_on',
- 'label': 'Show:',
- 'options' : [
- ("Today's builds" , 'started_on__gte:'+today_begin.strftime("%Y-%m-%d"), queryset_all.filter(started_on__gte=today_begin).count()),
- ("Yesterday's builds",
- 'started_on__gte!started_on__lt:'
- +yesterday_begin.strftime("%Y-%m-%d")+'!'
- +today_begin.strftime("%Y-%m-%d"),
- queryset_all.filter(
- started_on__gte=yesterday_begin,
- started_on__lt=today_begin
- ).count()),
- ("Build date range", 'daterange', 1, '', 'started_on'),
- ]
- }
- },
- {'name': 'Completed on',
- 'qhelp': "The date and time the build finished",
- 'orderfield': _get_toggle_order(request, "completed_on", True),
- 'ordericon':_get_toggle_order_icon(request, "completed_on"),
- 'orderkey' : 'completed_on',
- 'filter' : {'class' : 'completed_on',
- 'label': 'Show:',
- 'options' : [
- ("Today's builds" , 'completed_on__gte:'+today_begin.strftime("%Y-%m-%d"), queryset_all.filter(completed_on__gte=today_begin).count()),
- ("Yesterday's builds",
- 'completed_on__gte!completed_on__lt:'
- +yesterday_begin.strftime("%Y-%m-%d")+'!'
- +today_begin.strftime("%Y-%m-%d"),
- queryset_all.filter(
- completed_on__gte=yesterday_begin,
- completed_on__lt=today_begin
- ).count()),
- ("Build date range", 'daterange', 1, '', 'completed_on'),
- ]
- }
- },
- {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox
- 'qhelp': "How many tasks failed during the build",
- 'filter' : {'class' : 'failed_tasks',
- 'label': 'Show:',
- 'options' : [
- ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()),
- ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()),
- ]
- }
- },
- {'name': 'Errors', 'clclass': 'errors_no',
- 'qhelp': "How many errors were encountered during the build (if any)",
- # Comment out sorting and filter until YOCTO #8131 is fixed
- #'orderfield': _get_toggle_order(request, "errors_no", True),
- #'ordericon':_get_toggle_order_icon(request, "errors_no"),
- #'orderkey' : 'errors_no',
- #'filter' : {'class' : 'errors_no',
- # 'label': 'Show:',
- # 'options' : [
- # ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()),
- # ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()),
- # ]
- # }
- },
- {'name': 'Warnings', 'clclass': 'warnings_no',
- 'qhelp': "How many warnings were encountered during the build (if any)",
- # Comment out sorting and filter until YOCTO #8131 is fixed
- #'orderfield': _get_toggle_order(request, "warnings_no", True),
- #'ordericon':_get_toggle_order_icon(request, "warnings_no"),
- #'orderkey' : 'warnings_no',
- #'filter' : {'class' : 'warnings_no',
- # 'label': 'Show:',
- # 'options' : [
- # ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()),
- # ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()),
- # ]
- # }
- },
- {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
- 'qhelp': "How long it took the build to finish",
- # Comment out sorting until YOCTO #8131 is fixed
- #'orderfield': _get_toggle_order(request, "timespent", True),
- #'ordericon':_get_toggle_order_icon(request, "timespent"),
- #'orderkey' : 'timespent',
- },
- {'name': 'Image files', 'clclass': 'output',
- 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
- # TODO: compute image fstypes from Target_Image_File
- }
- ]
- }
-
- # merge daterange values
- context.update(context_date)
- return context, pagesize, orderby
-
-
-
# new project
def newproject(request):
template = "newproject.html"
@@ -2280,16 +2011,17 @@ if True:
prj.bitbake_version = prj.release.bitbake_version
prj.save()
# we need to change the layers
- for i in prj.projectlayer_set.all():
+ for project in prj.projectlayer_set.all():
# find and add a similarly-named layer on the new branch
try:
- lv = prj.compatible_layerversions(layer_name = i.layercommit.layer.name)[0]
- ProjectLayer.objects.get_or_create(project = prj, layercommit = lv)
+ layer_versions = prj.get_all_compatible_layer_versions()
+ layer_versions = layer_versions.filter(layer__name = project.layercommit.layer.name)
+ ProjectLayer.objects.get_or_create(project = prj, layercommit = layer_versions.first())
except IndexError:
pass
finally:
# get rid of the old entry
- i.delete()
+ project.delete()
if 'machineName' in request.POST:
machinevar = prj.projectvariable_set.get(name="MACHINE")
@@ -2316,7 +2048,7 @@ if True:
"completedbuilds": Build.objects.exclude(outcome = Build.IN_PROGRESS).filter(project_id = pid),
"prj" : {"name": prj.name, },
"buildrequests" : prj.build_set.filter(outcome=Build.IN_PROGRESS),
- "builds" : _project_recent_build_list(prj),
+ "builds" : Build.get_recent(prj),
"layers" : map(lambda x: {
"id": x.layercommit.pk,
"orderid": x.pk,
@@ -2371,13 +2103,23 @@ if True:
name = "_js_unit_test_prj_"
- # If there is an existing project by this name delete it. We don't want
- # Lots of duplicates cluttering up the projects.
+ # If there is an existing project by this name delete it.
+ # We don't want Lots of duplicates cluttering up the projects.
Project.objects.filter(name=name).delete()
- new_project = Project.objects.create_project(name=name, release=release)
+ new_project = Project.objects.create_project(name=name,
+ release=release)
+ # Add a layer
+ layer = new_project.get_all_compatible_layer_versions().first()
+
+ ProjectLayer.objects.get_or_create(layercommit=layer,
+ project=new_project)
- context = { 'project' : new_project }
+ # make sure we have a machine set for this project
+ ProjectVariable.objects.get_or_create(project=new_project,
+ name="MACHINE",
+ value="qemux86")
+ context = {'project': new_project}
return render(request, "js-unit-tests.html", context)
from django.views.decorators.csrf import csrf_exempt
@@ -2399,12 +2141,17 @@ if True:
retval = []
- for i in prj.projectlayer_set.all():
- lv = prj.compatible_layerversions(release = Release.objects.get(pk=new_release_id)).filter(layer__name = i.layercommit.layer.name)
+ for project in prj.projectlayer_set.all():
+ release = Release.objects.get(pk = new_release_id)
+
+ layer_versions = prj.get_all_compatible_layer_versions()
+ layer_versions = layer_versions.filter(release = release)
+ layer_versions = layer_versions.filter(layer__name = project.layercommit.layer.name)
+
# there is no layer_version with the new release id,
# and the same name
- if lv.count() < 1:
- retval.append(i)
+ if layer_versions.count() < 1:
+ retval.append(project)
return response({"error":"ok",
"rows" : map( _lv_to_dict(prj),
@@ -2461,6 +2208,10 @@ if True:
except ProjectVariable.DoesNotExist:
pass
try:
+ return_data['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value,
+ except ProjectVariable.DoesNotExist:
+ pass
+ try:
return_data['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value,
except ProjectVariable.DoesNotExist:
pass
@@ -2473,7 +2224,7 @@ if True:
except ProjectVariable.DoesNotExist:
pass
try:
- return_data['sdk_machine'] = ProjectVariable.objects.get(project = prj, name = "SDKMACHINE").value,
+ return_data['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value,
except ProjectVariable.DoesNotExist:
pass
@@ -2493,8 +2244,11 @@ if True:
layers_added = [];
# Rudimentary check for any possible html tags
- if "<" in request.POST:
- return HttpResponse(jsonfilter({"error": "Invalid character <"}), content_type = "application/json")
+ for val in request.POST.values():
+ if "<" in val:
+ return HttpResponse(jsonfilter(
+ {"error": "Invalid character <"}),
+ content_type="application/json")
prj = Project.objects.get(pk=request.POST['project_id'])
@@ -2669,33 +2423,106 @@ if True:
# create custom recipe
try:
- recipe = CustomImageRecipe.objects.create(
- name=request.POST["name"],
- base_recipe=params["base"],
- project=params["project"])
+
+ # Only allowed chars in name are a-z, 0-9 and -
+ if re.search(r'[^a-z|0-9|-]', request.POST["name"]):
+ return {"error": "invalid-name"}
+
+ custom_images = CustomImageRecipe.objects.all()
+
+ # Are there any recipes with this name already in our project?
+ existing_image_recipes_in_project = custom_images.filter(
+ name=request.POST["name"], project=params["project"])
+
+ if existing_image_recipes_in_project.count() > 0:
+ return {"error": "image-already-exists"}
+
+ # Are there any recipes with this name which aren't custom
+ # image recipes?
+ custom_image_ids = custom_images.values_list('id', flat=True)
+ existing_non_image_recipes = Recipe.objects.filter(
+ Q(name=request.POST["name"]) & ~Q(pk__in=custom_image_ids)
+ )
+
+ if existing_non_image_recipes.count() > 0:
+ return {"error": "recipe-already-exists"}
+
+ # create layer 'Custom layer' and verion if needed
+ layer = Layer.objects.get_or_create(
+ name=CustomImageRecipe.LAYER_NAME,
+ summary="Layer for custom recipes",
+ vcs_url="file:///toaster_created_layer")[0]
+
+ # Check if we have a layer version already
+ # We don't use get_or_create here because the dirpath will change
+ # and is a required field
+ lver = Layer_Version.objects.filter(Q(project=params['project']) &
+ Q(layer=layer) &
+ Q(build=None)).last()
+ if lver == None:
+ lver, created = Layer_Version.objects.get_or_create(
+ project=params['project'],
+ layer=layer,
+ dirpath="toaster_created_layer")
+
+ # Add a dependency on our layer to the base recipe's layer
+ LayerVersionDependency.objects.get_or_create(
+ layer_version=lver,
+ depends_on=params["base"].layer_version)
+
+ # Add it to our current project if needed
+ ProjectLayer.objects.get_or_create(project=params['project'],
+ layercommit=lver,
+ optional=False)
+
+ # Create the actual recipe
+ recipe, created = CustomImageRecipe.objects.get_or_create(
+ name=request.POST["name"],
+ base_recipe=params["base"],
+ project=params["project"],
+ layer_version=lver,
+ is_image=True)
+
+ # If we created the object then setup these fields. They may get
+ # overwritten later on and cause the get_or_create to create a
+ # duplicate if they've changed.
+ if created:
+ recipe.file_path = request.POST["name"]
+ recipe.license = "MIT"
+ recipe.version = "0.1"
+ recipe.save()
+
except Error as err:
return {"error": "Can't create custom recipe: %s" % err}
# Find the package list from the last build of this recipe/target
- build = Build.objects.filter(target__target=params['base'].name,
- project=params['project']).last()
-
- if build:
+ target = Target.objects.filter(Q(build__outcome=Build.SUCCEEDED) &
+ Q(build__project=params['project']) &
+ (Q(target=params['base'].name) |
+ Q(target=recipe.name))).last()
+ if target:
# Copy in every package
# We don't want these packages to be linked to anything because
# that underlying data may change e.g. delete a build
- for package in build.package_set.all():
- # Create the duplicate
- package.pk = None
- package.save()
- # Disassociate the package from the build
- package.build = None
- package.save()
- recipe.packages.add(package)
- else:
- logger.warn("No packages found for this base recipe")
+ for tpackage in target.target_installed_package_set.all():
+ try:
+ built_package = tpackage.package
+ # The package had no recipe information so is a ghost
+ # package skip it
+ if built_package.recipe == None:
+ continue;
+
+ config_package = CustomImagePackage.objects.get(
+ name=built_package.name)
+
+ recipe.includes_set.add(config_package)
+ except Exception as e:
+ logger.warning("Error adding package %s %s" %
+ (tpackage.package.name, e))
+ pass
return {"error": "ok",
+ "packages" : recipe.get_all_packages().count(),
"url": reverse('customrecipe', args=(params['project'].pk,
recipe.id))}
@@ -2720,34 +2547,105 @@ if True:
or
{"error": <error message>}
"""
- objects = CustomImageRecipe.objects.filter(id=recipe_id)
- if not objects:
+ try:
+ custom_recipe = CustomImageRecipe.objects.get(id=recipe_id)
+ except CustomImageRecipe.DoesNotExist:
return {"error": "Custom recipe with id=%s "
"not found" % recipe_id}
+
if request.method == 'GET':
- values = CustomImageRecipe.objects.filter(id=recipe_id).values()
- if values:
- return {"error": "ok", "info": values[0]}
- else:
- return {"error": "Custom recipe with id=%s "
- "not found" % recipe_id}
- return {"error": "ok", "info": objects.values()[0]}
+ info = {"id" : custom_recipe.id,
+ "name" : custom_recipe.name,
+ "base_recipe_id": custom_recipe.base_recipe.id,
+ "project_id": custom_recipe.project.id,
+ }
+
+ return {"error": "ok", "info": info}
+
elif request.method == 'DELETE':
- objects.delete()
+ custom_recipe.delete()
return {"error": "ok"}
else:
return {"error": "Method %s is not supported" % request.method}
+ def customrecipe_download(request, pid, recipe_id):
+ recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id)
+
+ file_data = recipe.generate_recipe_file_contents()
+
+ response = HttpResponse(file_data, content_type='text/plain')
+ response['Content-Disposition'] = \
+ 'attachment; filename="%s_%s.bb"' % (recipe.name,
+ recipe.version)
+
+ return response
+
+ def _traverse_dependents(next_package_id, rev_deps, all_current_packages, tree_level=0):
+ """
+ Recurse through reverse dependency tree for next_package_id.
+ Limit the reverse dependency search to packages not already scanned,
+ that is, not already in rev_deps.
+ Limit the scan to a depth (tree_level) not exceeding the count of
+ all packages in the custom image, and if that depth is exceeded
+ return False, pop out of the recursion, and write a warning
+ to the log, but this is unlikely, suggesting a dependency loop
+ not caught by bitbake.
+ On return, the input/output arg rev_deps is appended with queryset
+ dictionary elements, annotated for use in the customimage template.
+ The list has unsorted, but unique elements.
+ """
+ max_dependency_tree_depth = all_current_packages.count()
+ if tree_level >= max_dependency_tree_depth:
+ logger.warning(
+ "The number of reverse dependencies "
+ "for this package exceeds " + max_dependency_tree_depth +
+ " and the remaining reverse dependencies will not be removed")
+ return True
+
+ package = CustomImagePackage.objects.get(id=next_package_id)
+ dependents = \
+ package.package_dependencies_target.annotate(
+ name=F('package__name'),
+ pk=F('package__pk'),
+ size=F('package__size'),
+ ).values("name", "pk", "size").exclude(
+ ~Q(pk__in=all_current_packages)
+ )
+
+ for pkg in dependents:
+ if pkg in rev_deps:
+ # already seen, skip dependent search
+ continue
+
+ rev_deps.append(pkg)
+ if (_traverse_dependents(
+ pkg["pk"], rev_deps, all_current_packages, tree_level+1)):
+ return True
+
+ return False
+
+ def _get_all_dependents(package_id, all_current_packages):
+ """
+ Returns sorted list of recursive reverse dependencies for package_id,
+ as a list of dictionary items, by recursing through dependency
+ relationships.
+ """
+ rev_deps = []
+ _traverse_dependents(package_id, rev_deps, all_current_packages)
+ rev_deps = sorted(rev_deps, key=lambda x: x["name"])
+ return rev_deps
+
@xhr_response
def xhr_customrecipe_packages(request, recipe_id, package_id):
"""
ReST API to add/remove packages to/from custom recipe.
- Entry point: /xhr_customrecipe/<recipe_id>/packages/
+ Entry point: /xhr_customrecipe/<recipe_id>/packages/<package_id>
Methods:
PUT - Add package to the recipe
DELETE - Delete package from the recipe
+ GET - Get package information
Returns:
{"error": "ok"}
@@ -2760,26 +2658,149 @@ if True:
return {"error": "Custom recipe with id=%s "
"not found" % recipe_id}
- if request.method == 'GET' and not package_id:
- return {"error": "ok",
- "packages": list(recipe.packages.values_list('id'))}
+ if package_id:
+ try:
+ package = CustomImagePackage.objects.get(id=package_id)
+ except Package.DoesNotExist:
+ return {"error": "Package with id=%s "
+ "not found" % package_id}
- try:
- package = Package.objects.get(id=package_id)
- except Package.DoesNotExist:
- return {"error": "Package with id=%s "
- "not found" % package_id}
+ if request.method == 'GET':
+ # If no package_id then list the current packages
+ if not package_id:
+ total_size = 0
+ packages = recipe.get_all_packages().values("id",
+ "name",
+ "version",
+ "size")
+ for package in packages:
+ package['size_formatted'] = \
+ filtered_filesizeformat(package['size'])
+ total_size += package['size']
+
+ return {"error": "ok",
+ "packages" : list(packages),
+ "total" : len(packages),
+ "total_size" : total_size,
+ "total_size_formatted" :
+ filtered_filesizeformat(total_size)
+ }
+ else:
+ all_current_packages = recipe.get_all_packages()
+
+ # Dependencies for package which aren't satisfied by the
+ # current packages in the custom image recipe
+ deps = package.package_dependencies_source.annotate(
+ name=F('depends_on__name'),
+ pk=F('depends_on__pk'),
+ size=F('depends_on__size'),
+ ).values("name", "pk", "size").filter(
+ # There are two depends types we don't know why
+ (Q(dep_type=Package_Dependency.TYPE_TRDEPENDS) |
+ Q(dep_type=Package_Dependency.TYPE_RDEPENDS)) &
+ ~Q(pk__in=all_current_packages)
+ )
+
+ # Reverse dependencies which are needed by packages that are
+ # in the image. Recursive search providing all dependents,
+ # not just immediate dependents.
+ reverse_deps = _get_all_dependents(package_id, all_current_packages)
+ total_size_deps = 0
+ total_size_reverse_deps = 0
+
+ for dep in deps:
+ dep['size_formatted'] = \
+ filtered_filesizeformat(dep['size'])
+ total_size_deps += dep['size']
+
+ for dep in reverse_deps:
+ dep['size_formatted'] = \
+ filtered_filesizeformat(dep['size'])
+ total_size_reverse_deps += dep['size']
+
+
+ return {"error": "ok",
+ "id": package.pk,
+ "name": package.name,
+ "version": package.version,
+ "unsatisfied_dependencies": list(deps),
+ "unsatisfied_dependencies_size": total_size_deps,
+ "unsatisfied_dependencies_size_formatted":
+ filtered_filesizeformat(total_size_deps),
+ "reverse_dependencies": list(reverse_deps),
+ "reverse_dependencies_size": total_size_reverse_deps,
+ "reverse_dependencies_size_formatted":
+ filtered_filesizeformat(total_size_reverse_deps)}
+
+ included_packages = recipe.includes_set.values_list('pk', flat=True)
if request.method == 'PUT':
- recipe.packages.add(package)
+ # If we're adding back a package which used to be included in this
+ # image all we need to do is remove it from the excludes
+ if package.pk in included_packages:
+ try:
+ recipe.excludes_set.remove(package)
+ return {"error": "ok"}
+ except Package.DoesNotExist:
+ return {"error":
+ "Package %s not found in excludes but was in "
+ "included list" % package.name}
+
+ else:
+ recipe.appends_set.add(package)
+ # Make sure that package is not in the excludes set
+ try:
+ recipe.excludes_set.remove(package)
+ except:
+ pass
+ # Add the dependencies we think will be added to the recipe
+ # as a result of appending this package.
+ # TODO this should recurse down the entire deps tree
+ for dep in package.package_dependencies_source.all_depends():
+ try:
+ cust_package = CustomImagePackage.objects.get(
+ name=dep.depends_on.name)
+
+ recipe.includes_set.add(cust_package)
+ try:
+ # When adding the pre-requisite package, make
+ # sure it's not in the excluded list from a
+ # prior removal.
+ recipe.excludes_set.remove(cust_package)
+ except Package.DoesNotExist:
+ # Don't care if the package had never been excluded
+ pass
+ except:
+ logger.warning("Could not add package's suggested"
+ "dependencies to the list")
+
return {"error": "ok"}
+
elif request.method == 'DELETE':
- if package in recipe.packages.all():
- recipe.packages.remove(package)
+ try:
+ # If we're deleting a package which is included we need to
+ # Add it to the excludes list.
+ if package.pk in included_packages:
+ recipe.excludes_set.add(package)
+ else:
+ recipe.appends_set.remove(package)
+ all_current_packages = recipe.get_all_packages()
+ reverse_deps_dictlist = _get_all_dependents(package.pk, all_current_packages)
+ ids = [entry['pk'] for entry in reverse_deps_dictlist]
+ reverse_deps = CustomImagePackage.objects.filter(id__in=ids)
+ for r in reverse_deps:
+ try:
+ if r.id in included_packages:
+ recipe.excludes_set.add(r)
+ else:
+ recipe.appends_set.remove(r)
+ except:
+ pass
+
return {"error": "ok"}
- else:
- return {"error": "Package '%s' is not in the recipe '%s'" % \
- (package.name, recipe.name)}
+ except CustomImageRecipe.DoesNotExist:
+ return {"error": "Tried to remove package that wasn't present"}
+
else:
return {"error": "Method %s is not supported" % request.method}
@@ -2816,30 +2837,16 @@ if True:
}
vars_blacklist = {
- 'DL_DR','PARALLEL_MAKE','BB_NUMBER_THREADS','SSTATE_DIR',
+ 'PARALLEL_MAKE','BB_NUMBER_THREADS',
'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT',
- 'DL_DIR','PARALLEL_MAKE','SSTATE_DIR','SSTATE_DIR','SSTATE_MIRRORS','TMPDIR',
+ 'PARALLEL_MAKE','SSTATE_MIRRORS','TMPDIR',
'all_proxy','ftp_proxy','http_proxy ','https_proxy'
}
- vars_fstypes = {
- 'btrfs','cpio','cpio.gz','cpio.lz4','cpio.lzma','cpio.xz','cramfs',
- 'elf','ext2','ext2.bz2','ext2.gz','ext2.lzma', 'ext4', 'ext4.gz', 'ext3','ext3.gz','hddimg',
- 'iso','jffs2','jffs2.sum','squashfs','squashfs-lzo','squashfs-xz','tar.bz2',
- 'tar.lz4','tar.xz','tartar.gz','ubi','ubifs','vmdk'
- }
+ vars_fstypes = Target_Image_File.SUFFIXES
return(vars_managed,sorted(vars_fstypes),vars_blacklist)
- def customrecipe(request, pid, recipe_id):
- project = Project.objects.get(pk=pid)
- context = {'project' : project,
- 'projectlayers': [],
- 'recipe' : CustomImageRecipe.objects.get(pk=recipe_id)
- }
-
- return render(request, "customrecipe.html", context)
-
@_template_renderer("projectconf.html")
def projectconf(request, pid):
@@ -2870,6 +2877,19 @@ if True:
except ProjectVariable.DoesNotExist:
pass
try:
+ if ProjectVariable.objects.get(project = prj, name = "DL_DIR").value == "${TOPDIR}/../downloads":
+ be = BuildEnvironment.objects.get(pk = str(1))
+ dl_dir = os.path.join(dirname(be.builddir), "downloads")
+ context['dl_dir'] = dl_dir
+ pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "DL_DIR")
+ pv.value = dl_dir
+ pv.save()
+ else:
+ context['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value
+ context['dl_dir_defined'] = "1"
+ except ProjectVariable.DoesNotExist,BuildEnvironment.DoesNotExist:
+ pass
+ try:
context['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value
context['fstypes_defined'] = "1"
except ProjectVariable.DoesNotExist:
@@ -2885,268 +2905,78 @@ if True:
except ProjectVariable.DoesNotExist:
pass
try:
- context['sdk_machine'] = ProjectVariable.objects.get(project = prj, name = "SDKMACHINE").value
- context['sdk_machine_defined'] = "1"
- except ProjectVariable.DoesNotExist:
+ if ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value == "${TOPDIR}/../sstate-cache":
+ be = BuildEnvironment.objects.get(pk = str(1))
+ sstate_dir = os.path.join(dirname(be.builddir), "sstate-cache")
+ context['sstate_dir'] = sstate_dir
+ pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "SSTATE_DIR")
+ pv.value = sstate_dir
+ pv.save()
+ else:
+ context['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value
+ context['sstate_dir_defined'] = "1"
+ except ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist:
pass
return context
- # WARNING _build_list_helper() may raise a RedirectException, which
- # will set the GET parameters and redirect back to the
- # all-builds or projectbuilds page as appropriate;
- # TODO don't use exceptions to control program flow
- @_template_renderer('projectbuilds.html')
- def projectbuilds(request, pid):
- prj = Project.objects.get(id = pid)
-
- if request.method == "POST":
- # process any build request
-
- if 'buildCancel' in request.POST:
- for i in request.POST['buildCancel'].strip().split(" "):
- try:
- br = BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_QUEUED)
- br.state = BuildRequest.REQ_DELETED
- br.save()
- except BuildRequest.DoesNotExist:
- pass
-
- if 'buildDelete' in request.POST:
- for i in request.POST['buildDelete'].strip().split(" "):
- try:
- BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_DELETED).delete()
- except BuildRequest.DoesNotExist:
- pass
-
- if 'targets' in request.POST:
- ProjectTarget.objects.filter(project = prj).delete()
- s = str(request.POST['targets'])
- for t in s.translate(None, ";%|\"").split(" "):
- if ":" in t:
- target, task = t.split(":")
- else:
- target = t
- task = ""
- ProjectTarget.objects.create(project = prj,
- target = target,
- task = task)
- prj.schedule_build()
-
- queryset = Build.objects.filter(project_id = pid)
-
- redirect_page = resolve(request.path_info).url_name
-
- context, pagesize, orderby = _build_list_helper(request,
- queryset,
- redirect_page,
- pid)
-
- context['project'] = prj
- _set_parameters_values(pagesize, orderby, request)
-
- # add the most recent builds for this project
- context['mru'] = _get_latest_builds(prj)
-
- return context
+ def _file_names_for_artifact(build, artifact_type, artifact_id):
+ """
+ Return a tuple (file path, file name for the download response) for an
+ artifact of type artifact_type with ID artifact_id for build; if
+ artifact type is not supported, returns (None, None)
+ """
+ file_name = None
+ response_file_name = None
+ if artifact_type == "cookerlog":
+ file_name = build.cooker_log_path
+ response_file_name = "cooker.log"
- def _file_name_for_artifact(b, artifact_type, artifact_id):
- file_name = None
- # Target_Image_File file_name
- if artifact_type == "imagefile":
- file_name = Target_Image_File.objects.get(target__build = b, pk = artifact_id).file_name
+ elif artifact_type == "imagefile":
+ file_name = Target_Image_File.objects.get(target__build = build, pk = artifact_id).file_name
elif artifact_type == "buildartifact":
- file_name = BuildArtifact.objects.get(build = b, pk = artifact_id).file_name
+ file_name = BuildArtifact.objects.get(build = build, pk = artifact_id).file_name
- elif artifact_type == "licensemanifest":
- file_name = Target.objects.get(build = b, pk = artifact_id).license_manifest_path
+ elif artifact_type == "licensemanifest":
+ file_name = Target.objects.get(build = build, pk = artifact_id).license_manifest_path
elif artifact_type == "tasklogfile":
- file_name = Task.objects.get(build = b, pk = artifact_id).logfile
+ file_name = Task.objects.get(build = build, pk = artifact_id).logfile
elif artifact_type == "logmessagefile":
- file_name = LogMessage.objects.get(build = b, pk = artifact_id).pathname
- else:
- raise Exception("FIXME: artifact type %s not implemented" % (artifact_type))
+ file_name = LogMessage.objects.get(build = build, pk = artifact_id).pathname
- return file_name
+ if file_name and not response_file_name:
+ response_file_name = os.path.basename(file_name)
+ return (file_name, response_file_name)
def build_artifact(request, build_id, artifact_type, artifact_id):
- if artifact_type in ["cookerlog"]:
- try:
- build = Build.objects.get(pk = build_id)
- file_name = build.cooker_log_path
+ """
+ View which returns a build artifact file as a response
+ """
+ file_name = None
+ response_file_name = None
+
+ try:
+ build = Build.objects.get(pk = build_id)
+ file_name, response_file_name = _file_names_for_artifact(
+ build, artifact_type, artifact_id
+ )
+
+ if file_name and response_file_name:
fsock = open(file_name, "r")
content_type = MimeTypeFinder.get_mimetype(file_name)
response = HttpResponse(fsock, content_type = content_type)
- disposition = 'attachment; filename=cooker.log'
- response['Content-Disposition'] = disposition
+ disposition = "attachment; filename=" + response_file_name
+ response["Content-Disposition"] = disposition
return response
- except IOError:
- context = {
- 'build' : Build.objects.get(pk = build_id),
- }
- return render(request, "unavailable_artifact.html", context)
-
- else:
- # retrieve the artifact directly from the build environment
- return _get_be_artifact(request, build_id, artifact_type, artifact_id)
-
-
- def _get_be_artifact(request, build_id, artifact_type, artifact_id):
- try:
- b = Build.objects.get(pk=build_id)
- if b.buildrequest is None or b.buildrequest.environment is None:
- raise Exception("Artifact not available for download (missing build request or build environment)")
-
- file_name = _file_name_for_artifact(b, artifact_type, artifact_id)
- fsock = None
- content_type='application/force-download'
-
- if file_name is None:
- raise Exception("Could not handle artifact %s id %s" % (artifact_type, artifact_id))
else:
- content_type = MimeTypeFinder.get_mimetype(file_name)
- fsock = b.buildrequest.environment.get_artifact(file_name)
- file_name = os.path.basename(file_name) # we assume that the build environment system has the same path conventions as host
-
- response = HttpResponse(fsock, content_type = content_type)
-
- # returns a file from the environment
- response['Content-Disposition'] = 'attachment; filename=' + file_name
- return response
- except IOError:
- context = {
- 'build' : Build.objects.get(pk = build_id),
- }
- return render(request, "unavailable_artifact.html", context)
-
-
-
-
- @_template_renderer("projects.html")
- def projects(request):
- (pagesize, orderby) = _get_parameters_values(request, 10, 'updated:-')
- mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
- retval = _verify_parameters( request.GET, mandatory_parameters )
- if retval:
- raise RedirectException( 'all-projects', request.GET, mandatory_parameters )
-
- queryset_all = Project.objects.all()
-
- # annotate each project with its number of builds
- queryset_all = queryset_all.annotate(num_builds=Count('build'))
-
- # exclude the command line builds project if it has no builds
- q_default_with_builds = Q(is_default=True) & Q(num_builds__gt=0)
- queryset_all = queryset_all.filter(Q(is_default=False) |
- q_default_with_builds)
-
- # if in BUILD_MODE, exclude everything but the command line builds project
- if not toastermain.settings.BUILD_MODE:
- queryset_all = queryset_all.exclude(is_default=False)
-
- # boilerplate code that takes a request for an object type and returns a queryset
- # for that object type. copypasta for all needed table searches
- (filter_string, search_term, ordering_string) = _search_tuple(request, Project)
- queryset_with_search = _get_queryset(Project, queryset_all, None, search_term, ordering_string, '-updated')
- queryset = _get_queryset(Project, queryset_all, filter_string, search_term, ordering_string, '-updated')
-
- # retrieve the objects that will be displayed in the table; projects a paginator and gets a page range to display
- project_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
-
- # add fields needed in JSON dumps for API call support
- for p in project_info.object_list:
- p.id = p.pk
- p.projectPageUrl = reverse('project', args=(p.id,))
- p.layersTypeAheadUrl = reverse('xhr_layerstypeahead', args=(p.id,))
- p.recipesTypeAheadUrl = reverse('xhr_recipestypeahead', args=(p.id,))
- p.projectBuildsUrl = reverse('projectbuilds', args=(p.id,))
-
- # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
- build_mru = _get_latest_builds()
-
- # translate the project's build target strings
- fstypes_map = {};
- for project in project_info:
- try:
- targets = Target.objects.filter( build_id = project.get_last_build_id() )
- comma = "";
- extensions = "";
- for t in targets:
- if ( not t.is_image ):
- continue
- tif = Target_Image_File.objects.filter( target_id = t.id )
- for i in tif:
- s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
- if s == i.file_name:
- s=re.sub('.*\.', '', i.file_name)
- if None == re.search(s,extensions):
- extensions += comma + s
- comma = ", "
- fstypes_map[project.id]=extensions
- except (Target.DoesNotExist,IndexError):
- fstypes_map[project.id]=project.get_last_imgfiles
-
- context = {
- 'mru' : build_mru,
-
- 'objects' : project_info,
- 'objectname' : "projects",
- 'default_orderby' : 'id:-',
- 'search_term' : search_term,
- 'total_count' : queryset_with_search.count(),
- 'fstypes' : fstypes_map,
- 'build_FAILED' : Build.FAILED,
- 'build_SUCCEEDED' : Build.SUCCEEDED,
- 'tablecols': [
- {'name': 'Project',
- 'orderfield': _get_toggle_order(request, "name"),
- 'ordericon':_get_toggle_order_icon(request, "name"),
- 'orderkey' : 'name',
- },
- {'name': 'Last activity on',
- 'clclass': 'updated',
- 'qhelp': "Shows the starting date and time of the last project build. If the project has no builds, it shows the date the project was created",
- 'orderfield': _get_toggle_order(request, "updated", True),
- 'ordericon':_get_toggle_order_icon(request, "updated"),
- 'orderkey' : 'updated',
- },
- {'name': 'Release',
- 'qhelp' : "The version of the build system used by the project",
- 'orderfield': _get_toggle_order(request, "release__name"),
- 'ordericon':_get_toggle_order_icon(request, "release__name"),
- 'orderkey' : 'release__name',
- },
- {'name': 'Machine',
- 'qhelp': "The hardware currently selected for the project",
- },
- {'name': 'Number of builds',
- 'qhelp': "How many builds have been run for the project",
- },
- {'name': 'Last build outcome', 'clclass': 'loutcome',
- 'qhelp': "Tells you if the last project build completed successfully or failed",
- },
- {'name': 'Recipe', 'clclass': 'ltarget',
- 'qhelp': "The last recipe that was built in this project",
- },
- {'name': 'Errors', 'clclass': 'lerrors',
- 'qhelp': "How many errors were encountered during the last project build (if any)",
- },
- {'name': 'Warnings', 'clclass': 'lwarnings',
- 'qhelp': "How many warnigns were encountered during the last project build (if any)",
- },
- {'name': 'Image files', 'clclass': 'limagefiles', 'hidden': 1,
- 'qhelp': "The root file system types produced by the last project build",
- },
- ]
- }
-
- _set_parameters_values(pagesize, orderby, request)
- return context
+ return render(request, "unavailable_artifact.html")
+ except ObjectDoesNotExist, IOError:
+ return render(request, "unavailable_artifact.html")