blob: 4e8f69e8010e3bc0092e96cab8f9ee18422be0cf [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#
2# ex:ts=4:sw=4:sts=4:et
3# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4#
5# BitBake Toaster Implementation
6#
7# Copyright (C) 2013 Intel Corporation
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22# pylint: disable=method-hidden
23# Gives E:848, 4: An attribute defined in json.encoder line 162 hides this method (method-hidden)
24# which is an invalid warning
25
26import operator,re
27
28from django.db.models import F, Q, Sum, Count, Max
29from django.db import IntegrityError
30from django.shortcuts import render, redirect
31from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable
32from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency
33from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact
34from orm.models import BitbakeVersion
35from bldcontrol import bbcontroller
36from django.views.decorators.cache import cache_control
37from django.core.urlresolvers import reverse, resolve
38from django.core.exceptions import MultipleObjectsReturned
39from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
40from django.http import HttpResponseBadRequest, HttpResponseNotFound
41from django.utils import timezone
42from django.utils.html import escape
43from datetime import timedelta, datetime, date
44from django.utils import formats
45from toastergui.templatetags.projecttags import json as jsonfilter
46import json
47from os.path import dirname
48import itertools
49
50import logging
51
52logger = logging.getLogger("toaster")
53
54# all new sessions should come through the landing page;
55# determine in which mode we are running in, and redirect appropriately
56def landing(request):
57 # we only redirect to projects page if there is a user-generated project
58 user_projects = Project.objects.filter(is_default = False)
59 has_user_project = user_projects.count() > 0
60
61 if Build.objects.count() == 0 and has_user_project:
62 return redirect(reverse('all-projects'), permanent = False)
63
64 if Build.objects.all().count() > 0:
65 return redirect(reverse('all-builds'), permanent = False)
66
67 context = {'lvs_nos' : Layer_Version.objects.all().count()}
68
69 return render(request, 'landing.html', context)
70
71
72
73# returns a list for most recent builds;
74def _get_latest_builds(prj=None):
75 queryset = Build.objects.all()
76
77 if prj is not None:
78 queryset = queryset.filter(project = prj)
79
80 return list(itertools.chain(
81 queryset.filter(outcome=Build.IN_PROGRESS).order_by("-pk"),
82 queryset.filter(outcome__lt=Build.IN_PROGRESS).order_by("-pk")[:3] ))
83
84
85# a JSON-able dict of recent builds; for use in the Project page, xhr_ updates, and other places, as needed
86def _project_recent_build_list(prj):
87 data = []
88 # take the most recent 3 completed builds, plus any builds in progress
89 for x in _get_latest_builds(prj):
90 d = {
91 "id": x.pk,
92 "targets" : map(lambda y: {"target": y.target, "task": y.task }, x.target_set.all()), # TODO: create the task entry in the Target table
93 "status": x.get_current_status(),
94 "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))),
95 "updated": x.completed_on.strftime('%s')+"000",
96 "command_time": (x.completed_on - x.started_on).total_seconds(),
97 "br_page_url": reverse('builddashboard', args=(x.pk,) ),
98 "build" : map( lambda y: {"id": y.pk,
99 "status": y.get_outcome_display(),
100 "completed_on" : y.completed_on.strftime('%s')+"000",
101 "build_time" : (y.completed_on - y.started_on).total_seconds(),
102 "build_page_url" : reverse('builddashboard', args=(y.pk,)),
103 'build_time_page_url': reverse('buildtime', args=(y.pk,)),
104 "errors": y.errors.count(),
105 "warnings": y.warnings.count(),
106 "completeper": y.completeper() if y.outcome == Build.IN_PROGRESS else "0",
107 "eta": y.eta().strftime('%s')+"000" if y.outcome == Build.IN_PROGRESS else "0",
108 }, [x]),
109 }
110 data.append(d)
111
112 return data
113
114
115
116def objtojson(obj):
117 from django.db.models.query import QuerySet
118 from django.db.models import Model
119
120 if isinstance(obj, datetime):
121 return obj.isoformat()
122 elif isinstance(obj, timedelta):
123 return obj.total_seconds()
124 elif isinstance(obj, QuerySet) or isinstance(obj, set):
125 return list(obj)
126 elif type(obj).__name__ == "RelatedManager":
127 return [x.pk for x in obj.all()]
128 elif hasattr( obj, '__dict__') and isinstance(obj, Model):
129 d = obj.__dict__
130 nd = dict(d)
131 for di in d.keys():
132 if di.startswith("_"):
133 del nd[di]
134 elif isinstance(d[di], Model):
135 nd[di] = d[di].pk
136 elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di):
137 nd[di] = getattr(obj, "get_%s_display" % di)()
138 return nd
139 elif isinstance( obj, type(lambda x:x)):
140 import inspect
141 return inspect.getsourcelines(obj)[0]
142 else:
143 raise TypeError("Unserializable object %s (%s) of type %s" % ( obj, dir(obj), type(obj)))
144
145
146def _template_renderer(template):
147 def func_wrapper(view):
148 def returned_wrapper(request, *args, **kwargs):
149 try:
150 context = view(request, *args, **kwargs)
151 except RedirectException as e:
152 return e.get_redirect_response()
153
154 if request.GET.get('format', None) == 'json':
155 # objects is a special keyword - it's a Page, but we need the actual objects here
156 # in XHR, the objects come in the "rows" property
157 if "objects" in context:
158 context["rows"] = context["objects"].object_list
159 del context["objects"]
160
161 # we're about to return; to keep up with the XHR API, we set the error to OK
162 context["error"] = "ok"
163
164 return HttpResponse(jsonfilter(context, default=objtojson ),
165 content_type = "application/json; charset=utf-8")
166 else:
167 return render(request, template, context)
168 return returned_wrapper
169 return func_wrapper
170
171
172def _lv_to_dict(prj, x = None):
173 if x is None:
174 def wrapper(x):
175 return _lv_to_dict(prj, x)
176 return wrapper
177
178 return {"id": x.pk,
179 "name": x.layer.name,
180 "tooltip": "%s | %s" % (x.layer.vcs_url,x.get_vcs_reference()),
181 "detail": "(%s" % x.layer.vcs_url + (")" if x.up_branch == None else " | "+x.get_vcs_reference()+")"),
182 "giturl": x.layer.vcs_url,
183 "layerdetailurl" : reverse('layerdetails', args=(prj.id,x.pk)),
184 "revision" : x.get_vcs_reference(),
185 }
186
187
188def _build_page_range(paginator, index = 1):
189 try:
190 page = paginator.page(index)
191 except PageNotAnInteger:
192 page = paginator.page(1)
193 except EmptyPage:
194 page = paginator.page(paginator.num_pages)
195
196
197 page.page_range = [page.number]
198 crt_range = 0
199 for i in range(1,5):
200 if (page.number + i) <= paginator.num_pages:
201 page.page_range = page.page_range + [ page.number + i]
202 crt_range +=1
203 if (page.number - i) > 0:
204 page.page_range = [page.number -i] + page.page_range
205 crt_range +=1
206 if crt_range == 4:
207 break
208 return page
209
210
211def _verify_parameters(g, mandatory_parameters):
212 miss = []
213 for mp in mandatory_parameters:
214 if not mp in g:
215 miss.append(mp)
216 if len(miss):
217 return miss
218 return None
219
220def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
221 import urllib
222 url = reverse(view, kwargs=kwargs)
223 params = {}
224 for i in g:
225 params[i] = g[i]
226 for i in mandatory_parameters:
227 if not i in params:
228 params[i] = urllib.unquote(str(mandatory_parameters[i]))
229
230 return redirect(url + "?%s" % urllib.urlencode(params), permanent = False, **kwargs)
231
232class RedirectException(Exception):
233 def __init__(self, view, g, mandatory_parameters, *args, **kwargs):
234 super(RedirectException, self).__init__()
235 self.view = view
236 self.g = g
237 self.mandatory_parameters = mandatory_parameters
238 self.oargs = args
239 self.okwargs = kwargs
240
241 def get_redirect_response(self):
242 return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, **self.okwargs)
243
244FIELD_SEPARATOR = ":"
245AND_VALUE_SEPARATOR = "!"
246OR_VALUE_SEPARATOR = "|"
247DESCENDING = "-"
248
249def __get_q_for_val(name, value):
250 if "OR" in value:
251 return reduce(operator.or_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("OR") ]))
252 if "AND" in value:
253 return reduce(operator.and_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("AND") ]))
254 if value.startswith("NOT"):
255 value = value[3:]
256 if value == 'None':
257 value = None
258 kwargs = { name : value }
259 return ~Q(**kwargs)
260 else:
261 if value == 'None':
262 value = None
263 kwargs = { name : value }
264 return Q(**kwargs)
265
266def _get_filtering_query(filter_string):
267
268 search_terms = filter_string.split(FIELD_SEPARATOR)
269 and_keys = search_terms[0].split(AND_VALUE_SEPARATOR)
270 and_values = search_terms[1].split(AND_VALUE_SEPARATOR)
271
272 and_query = []
273 for kv in zip(and_keys, and_values):
274 or_keys = kv[0].split(OR_VALUE_SEPARATOR)
275 or_values = kv[1].split(OR_VALUE_SEPARATOR)
276 querydict = dict(zip(or_keys, or_values))
277 and_query.append(reduce(operator.or_, map(lambda x: __get_q_for_val(x, querydict[x]), [k for k in querydict])))
278
279 return reduce(operator.and_, [k for k in and_query])
280
281def _get_toggle_order(request, orderkey, toggle_reverse = False):
282 if toggle_reverse:
283 return "%s:+" % orderkey if request.GET.get('orderby', "") == "%s:-" % orderkey else "%s:-" % orderkey
284 else:
285 return "%s:-" % orderkey if request.GET.get('orderby', "") == "%s:+" % orderkey else "%s:+" % orderkey
286
287def _get_toggle_order_icon(request, orderkey):
288 if request.GET.get('orderby', "") == "%s:+"%orderkey:
289 return "down"
290 elif request.GET.get('orderby', "") == "%s:-"%orderkey:
291 return "up"
292 else:
293 return None
294
295# we check that the input comes in a valid form that we can recognize
296def _validate_input(field_input, model):
297
298 invalid = None
299
300 if field_input:
301 field_input_list = field_input.split(FIELD_SEPARATOR)
302
303 # Check we have only one colon
304 if len(field_input_list) != 2:
305 invalid = "We have an invalid number of separators: " + field_input + " -> " + str(field_input_list)
306 return None, invalid
307
308 # Check we have an equal number of terms both sides of the colon
309 if len(field_input_list[0].split(AND_VALUE_SEPARATOR)) != len(field_input_list[1].split(AND_VALUE_SEPARATOR)):
310 invalid = "Not all arg names got values"
311 return None, invalid + str(field_input_list)
312
313 # Check we are looking for a valid field
314 valid_fields = model._meta.get_all_field_names()
315 for field in field_input_list[0].split(AND_VALUE_SEPARATOR):
316 if not reduce(lambda x, y: x or y, [ field.startswith(x) for x in valid_fields ]):
317 return None, (field, [ x for x in valid_fields ])
318
319 return field_input, invalid
320
321# uses search_allowed_fields in orm/models.py to create a search query
322# for these fields with the supplied input text
323def _get_search_results(search_term, queryset, model):
324 search_objects = []
325 for st in search_term.split(" "):
326 q_map = map(lambda x: Q(**{x+'__icontains': st}),
327 model.search_allowed_fields)
328
329 search_objects.append(reduce(operator.or_, q_map))
330 search_object = reduce(operator.and_, search_objects)
331 queryset = queryset.filter(search_object)
332
333 return queryset
334
335
336# function to extract the search/filter/ordering parameters from the request
337# it uses the request and the model to validate input for the filter and orderby values
338def _search_tuple(request, model):
339 ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model)
340 if invalid:
341 raise BaseException("Invalid ordering model:" + str(model) + str(invalid))
342
343 filter_string, invalid = _validate_input(request.GET.get('filter', ''), model)
344 if invalid:
345 raise BaseException("Invalid filter " + str(invalid))
346
347 search_term = request.GET.get('search', '')
348 return (filter_string, search_term, ordering_string)
349
350
351# returns a lazy-evaluated queryset for a filter/search/order combination
352def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''):
353 if filter_string:
354 filter_query = _get_filtering_query(filter_string)
355 queryset = queryset.filter(filter_query)
356 else:
357 queryset = queryset.all()
358
359 if search_term:
360 queryset = _get_search_results(search_term, queryset, model)
361
362 if ordering_string:
363 column, order = ordering_string.split(':')
364 if column == re.sub('-','',ordering_secondary):
365 ordering_secondary=''
366 if order.lower() == DESCENDING:
367 column = '-' + column
368 if ordering_secondary:
369 queryset = queryset.order_by(column, ordering_secondary)
370 else:
371 queryset = queryset.order_by(column)
372
373 # insure only distinct records (e.g. from multiple search hits) are returned
374 return queryset.distinct()
375
376# returns the value of entries per page and the name of the applied sorting field.
377# if the value is given explicitly as a GET parameter it will be the first selected,
378# otherwise the cookie value will be used.
379def _get_parameters_values(request, default_count, default_order):
380 current_url = resolve(request.path_info).url_name
381 pagesize = request.GET.get('count', request.session.get('%s_count' % current_url, default_count))
382 orderby = request.GET.get('orderby', request.session.get('%s_orderby' % current_url, default_order))
383 return (pagesize, orderby)
384
385
386# set cookies for parameters. this is usefull in case parameters are set
387# manually from the GET values of the link
388def _set_parameters_values(pagesize, orderby, request):
389 from django.core.urlresolvers import resolve
390 current_url = resolve(request.path_info).url_name
391 request.session['%s_count' % current_url] = pagesize
392 request.session['%s_orderby' % current_url] =orderby
393
394# date range: normalize GUI's dd/mm/yyyy to date object
395def _normalize_input_date(date_str,default):
396 date_str=re.sub('/', '-', date_str)
397 # accept dd/mm/yyyy to d/m/yy
398 try:
399 date_in = datetime.strptime(date_str, "%d-%m-%Y")
400 except ValueError:
401 # courtesy try with two digit year
402 try:
403 date_in = datetime.strptime(date_str, "%d-%m-%y")
404 except ValueError:
405 return default
406 date_in = date_in.replace(tzinfo=default.tzinfo)
407 return date_in
408
409# convert and normalize any received date range filter, for example:
410# "completed_on__gte!completed_on__lt:01/03/2015!02/03/2015_daterange" to
411# "completed_on__gte!completed_on__lt:2015-03-01!2015-03-02"
412def _modify_date_range_filter(filter_string):
413 # was the date range radio button selected?
414 if 0 > filter_string.find('_daterange'):
415 return filter_string,''
416 # normalize GUI dates to database format
417 filter_string = filter_string.replace('_daterange','').replace(':','!');
418 filter_list = filter_string.split('!');
419 if 4 != len(filter_list):
420 return filter_string
421 today = timezone.localtime(timezone.now())
422 date_id = filter_list[1]
423 date_from = _normalize_input_date(filter_list[2],today)
424 date_to = _normalize_input_date(filter_list[3],today)
425 # swap dates if manually set dates are out of order
426 if date_to < date_from:
427 date_to,date_from = date_from,date_to
428 # convert to strings, make 'date_to' inclusive by moving to begining of next day
429 date_from_str = date_from.strftime("%Y-%m-%d")
430 date_to_str = (date_to+timedelta(days=1)).strftime("%Y-%m-%d")
431 filter_string=filter_list[0]+'!'+filter_list[1]+':'+date_from_str+'!'+date_to_str
432 daterange_selected = re.sub('__.*','', date_id)
433 return filter_string,daterange_selected
434
435def _add_daterange_context(queryset_all, request, daterange_list):
436 # calculate the exact begining of local today and yesterday
437 today_begin = timezone.localtime(timezone.now())
438 today_begin = date(today_begin.year,today_begin.month,today_begin.day)
439 yesterday_begin = today_begin-timedelta(days=1)
440 # add daterange persistent
441 context_date = {}
442 context_date['last_date_from'] = request.GET.get('last_date_from',timezone.localtime(timezone.now()).strftime("%d/%m/%Y"))
443 context_date['last_date_to' ] = request.GET.get('last_date_to' ,context_date['last_date_from'])
444 # calculate the date ranges, avoid second sort for 'created'
445 # fetch the respective max range from the database
446 context_date['daterange_filter']=''
447 for key in daterange_list:
448 queryset_key = queryset_all.order_by(key)
449 try:
450 context_date['dateMin_'+key]=timezone.localtime(getattr(queryset_key.first(),key)).strftime("%d/%m/%Y")
451 except AttributeError:
452 context_date['dateMin_'+key]=timezone.localtime(timezone.now())
453 try:
454 context_date['dateMax_'+key]=timezone.localtime(getattr(queryset_key.last(),key)).strftime("%d/%m/%Y")
455 except AttributeError:
456 context_date['dateMax_'+key]=timezone.localtime(timezone.now())
457 return context_date,today_begin,yesterday_begin
458
459
460##
461# build dashboard for a single build, coming in as argument
462# Each build may contain multiple targets and each target
463# may generate multiple image files. display them all.
464#
465def builddashboard( request, build_id ):
466 template = "builddashboard.html"
467 if Build.objects.filter( pk=build_id ).count( ) == 0 :
468 return redirect( builds )
469 build = Build.objects.get( pk = build_id );
470 layerVersionId = Layer_Version.objects.filter( build = build_id );
471 recipeCount = Recipe.objects.filter( layer_version__id__in = layerVersionId ).count( );
472 tgts = Target.objects.filter( build_id = build_id ).order_by( 'target' );
473
474 ##
475 # set up custom target list with computed package and image data
476 #
477
478 targets = [ ]
479 ntargets = 0
480 hasImages = False
481 targetHasNoImages = False
482 for t in tgts:
483 elem = { }
484 elem[ 'target' ] = t
485 if ( t.is_image ):
486 hasImages = True
487 npkg = 0
488 pkgsz = 0
489 package = None
490 for package in Package.objects.filter(id__in = [x.package_id for x in t.target_installed_package_set.all()]):
491 pkgsz = pkgsz + package.size
492 if ( package.installed_name ):
493 npkg = npkg + 1
494 elem[ 'npkg' ] = npkg
495 elem[ 'pkgsz' ] = pkgsz
496 ti = Target_Image_File.objects.filter( target_id = t.id )
497 imageFiles = [ ]
498 for i in ti:
499 ndx = i.file_name.rfind( '/' )
500 if ( ndx < 0 ):
501 ndx = 0;
502 f = i.file_name[ ndx + 1: ]
503 imageFiles.append({ 'id': i.id, 'path': f, 'size' : i.file_size })
504 if ( t.is_image and
505 (( len( imageFiles ) <= 0 ) or ( len( t.license_manifest_path ) <= 0 ))):
506 targetHasNoImages = True
507 elem[ 'imageFiles' ] = imageFiles
508 elem[ 'targetHasNoImages' ] = targetHasNoImages
509 targets.append( elem )
510
511 ##
512 # how many packages in this build - ignore anonymous ones
513 #
514
515 packageCount = 0
516 packages = Package.objects.filter( build_id = build_id )
517 for p in packages:
518 if ( p.installed_name ):
519 packageCount = packageCount + 1
520
521 logmessages = list(LogMessage.objects.filter( build = build_id ))
522
523 context = {
524 'build' : build,
525 'hasImages' : hasImages,
526 'ntargets' : ntargets,
527 'targets' : targets,
528 'recipecount' : recipeCount,
529 'packagecount' : packageCount,
530 'logmessages' : logmessages,
531 }
532 return render( request, template, context )
533
534
535
536def generateCoveredList2( revlist = None ):
537 if not revlist:
538 revlist = []
539 covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
540 while len(covered_list):
541 revlist = [ x for x in revlist if x.outcome != Task.OUTCOME_COVERED ]
542 if len(revlist) > 0:
543 return revlist
544
545 newlist = _find_task_revdep_list(covered_list)
546
547 revlist = list(set(revlist + newlist))
548 covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
549 return revlist
550
551def task( request, build_id, task_id ):
552 template = "task.html"
553 tasks_list = Task.objects.filter( pk=task_id )
554 if tasks_list.count( ) == 0:
555 return redirect( builds )
556 task_object = tasks_list[ 0 ];
557 dependencies = sorted(
558 _find_task_dep( task_object ),
559 key=lambda t:'%s_%s %s'%(t.recipe.name, t.recipe.version, t.task_name))
560 reverse_dependencies = sorted(
561 _find_task_revdep( task_object ),
562 key=lambda t:'%s_%s %s'%( t.recipe.name, t.recipe.version, t.task_name ))
563 coveredBy = '';
564 if ( task_object.outcome == Task.OUTCOME_COVERED ):
565# _list = generateCoveredList( task )
566 coveredBy = sorted(generateCoveredList2( _find_task_revdep( task_object ) ), key = lambda x: x.recipe.name)
567 log_head = ''
568 log_body = ''
569 if task_object.outcome == task_object.OUTCOME_FAILED:
570 pass
571
572 uri_list= [ ]
573 variables = Variable.objects.filter(build=build_id)
574 v=variables.filter(variable_name='SSTATE_DIR')
575 if v.count() > 0:
576 uri_list.append(v[0].variable_value)
577 v=variables.filter(variable_name='SSTATE_MIRRORS')
578 if (v.count() > 0):
579 for mirror in v[0].variable_value.split('\\n'):
580 s=re.sub('.* ','',mirror.strip(' \t\n\r'))
581 if len(s):
582 uri_list.append(s)
583
584 context = {
585 'build' : Build.objects.filter( pk = build_id )[ 0 ],
586 'object' : task_object,
587 'task' : task_object,
588 'covered_by' : coveredBy,
589 'deps' : dependencies,
590 'rdeps' : reverse_dependencies,
591 'log_head' : log_head,
592 'log_body' : log_body,
593 'showing_matches' : False,
594 'uri_list' : uri_list,
595 }
596 if request.GET.get( 'show_matches', "" ):
597 context[ 'showing_matches' ] = True
598 context[ 'matching_tasks' ] = Task.objects.filter(
599 sstate_checksum=task_object.sstate_checksum ).filter(
600 build__completed_on__lt=task_object.build.completed_on).exclude(
601 order__isnull=True).exclude(outcome=Task.OUTCOME_NA).order_by('-build__completed_on')
602
603 return render( request, template, context )
604
605def recipe(request, build_id, recipe_id, active_tab="1"):
606 template = "recipe.html"
607 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
608 return redirect(builds)
609
610 recipe_object = Recipe.objects.get(pk=recipe_id)
611 layer_version = Layer_Version.objects.get(pk=recipe_object.layer_version_id)
612 layer = Layer.objects.get(pk=layer_version.layer_id)
613 tasks_list = Task.objects.filter(recipe_id = recipe_id, build_id = build_id).exclude(order__isnull=True).exclude(task_name__endswith='_setscene').exclude(outcome=Task.OUTCOME_NA)
614 package_count = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0).count()
615
616 if active_tab != '1' and active_tab != '3' and active_tab != '4' :
617 active_tab = '1'
618 tab_states = {'1': '', '3': '', '4': ''}
619 tab_states[active_tab] = 'active'
620
621 context = {
622 'build' : Build.objects.get(pk=build_id),
623 'object' : recipe_object,
624 'layer_version' : layer_version,
625 'layer' : layer,
626 'tasks' : tasks_list,
627 'package_count' : package_count,
628 'tab_states' : tab_states,
629 }
630 return render(request, template, context)
631
632def recipe_packages(request, build_id, recipe_id):
633 template = "recipe_packages.html"
634 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
635 return redirect(builds)
636
637 (pagesize, orderby) = _get_parameters_values(request, 10, 'name:+')
638 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
639 retval = _verify_parameters( request.GET, mandatory_parameters )
640 if retval:
641 return _redirect_parameters( 'recipe_packages', request.GET, mandatory_parameters, build_id = build_id, recipe_id = recipe_id)
642 (filter_string, search_term, ordering_string) = _search_tuple(request, Package)
643
644 recipe_object = Recipe.objects.get(pk=recipe_id)
645 queryset = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0)
646 package_count = queryset.count()
647 queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name')
648
649 packages = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
650
651 context = {
652 'build' : Build.objects.get(pk=build_id),
653 'recipe' : recipe_object,
654 'objects' : packages,
655 'object_count' : package_count,
656 'tablecols':[
657 {
658 'name':'Package',
659 'orderfield': _get_toggle_order(request,"name"),
660 'ordericon': _get_toggle_order_icon(request,"name"),
661 'orderkey': "name",
662 },
663 {
664 'name':'Version',
665 },
666 {
667 'name':'Size',
668 'orderfield': _get_toggle_order(request,"size", True),
669 'ordericon': _get_toggle_order_icon(request,"size"),
670 'orderkey': 'size',
671 'dclass': 'sizecol span2',
672 },
673 ]
674 }
675 response = render(request, template, context)
676 _set_parameters_values(pagesize, orderby, request)
677 return response
678
679def target_common( request, build_id, target_id, variant ):
680 template = "target.html"
681 (pagesize, orderby) = _get_parameters_values(request, 25, 'name:+')
682 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
683 retval = _verify_parameters( request.GET, mandatory_parameters )
684 if retval:
685 return _redirect_parameters(
686 variant, request.GET, mandatory_parameters,
687 build_id = build_id, target_id = target_id )
688 ( filter_string, search_term, ordering_string ) = _search_tuple( request, Package )
689
690 # FUTURE: get rid of nested sub-queries replacing with ManyToMany field
691 queryset = Package.objects.filter(
692 size__gte = 0,
693 id__in = Target_Installed_Package.objects.filter(
694 target_id=target_id ).values( 'package_id' ))
695 packages_sum = queryset.aggregate( Sum( 'installed_size' ))
696 queryset = _get_queryset(
697 Package, queryset, filter_string, search_term, ordering_string, 'name' )
698 queryset = queryset.select_related("recipe", "recipe__layer_version", "recipe__layer_version__layer")
699 packages = _build_page_range( Paginator(queryset, pagesize), request.GET.get( 'page', 1 ))
700
701
702
703 build = Build.objects.get( pk = build_id )
704
705 # bring in package dependencies
706 for p in packages.object_list:
707 p.runtime_dependencies = p.package_dependencies_source.filter(
708 target_id = target_id, dep_type=Package_Dependency.TYPE_TRDEPENDS ).select_related("depends_on")
709 p.reverse_runtime_dependencies = p.package_dependencies_target.filter(
710 target_id = target_id, dep_type=Package_Dependency.TYPE_TRDEPENDS ).select_related("package")
711 tc_package = {
712 'name' : 'Package',
713 'qhelp' : 'Packaged output resulting from building a recipe included in this image',
714 'orderfield' : _get_toggle_order( request, "name" ),
715 'ordericon' : _get_toggle_order_icon( request, "name" ),
716 }
717 tc_packageVersion = {
718 'name' : 'Package version',
719 'qhelp' : 'The package version and revision',
720 }
721 tc_size = {
722 'name' : 'Size',
723 'qhelp' : 'The size of the package',
724 'orderfield' : _get_toggle_order( request, "size", True ),
725 'ordericon' : _get_toggle_order_icon( request, "size" ),
726 'orderkey' : 'size',
727 'clclass' : 'size',
728 'dclass' : 'span2',
729 }
730 if ( variant == 'target' ):
731 tc_size[ "hidden" ] = 0
732 else:
733 tc_size[ "hidden" ] = 1
734 tc_sizePercentage = {
735 'name' : 'Size over total (%)',
736 'qhelp' : 'Proportion of the overall size represented by this package',
737 'clclass' : 'size_over_total',
738 'hidden' : 1,
739 }
740 tc_license = {
741 'name' : 'License',
742 'qhelp' : 'The license under which the package is distributed. Separate license names u\
743sing | (pipe) means there is a choice between licenses. Separate license names using & (ampersand) m\
744eans multiple licenses exist that cover different parts of the source',
745 'orderfield' : _get_toggle_order( request, "license" ),
746 'ordericon' : _get_toggle_order_icon( request, "license" ),
747 'orderkey' : 'license',
748 'clclass' : 'license',
749 }
750 if ( variant == 'target' ):
751 tc_license[ "hidden" ] = 1
752 else:
753 tc_license[ "hidden" ] = 0
754 tc_dependencies = {
755 'name' : 'Dependencies',
756 'qhelp' : "Package runtime dependencies (other packages)",
757 'clclass' : 'depends',
758 }
759 if ( variant == 'target' ):
760 tc_dependencies[ "hidden" ] = 0
761 else:
762 tc_dependencies[ "hidden" ] = 1
763 tc_rdependencies = {
764 'name' : 'Reverse dependencies',
765 'qhelp' : 'Package run-time reverse dependencies (i.e. which other packages depend on this package',
766 'clclass' : 'brought_in_by',
767 }
768 if ( variant == 'target' ):
769 tc_rdependencies[ "hidden" ] = 0
770 else:
771 tc_rdependencies[ "hidden" ] = 1
772 tc_recipe = {
773 'name' : 'Recipe',
774 'qhelp' : 'The name of the recipe building the package',
775 'orderfield' : _get_toggle_order( request, "recipe__name" ),
776 'ordericon' : _get_toggle_order_icon( request, "recipe__name" ),
777 'orderkey' : "recipe__name",
778 'clclass' : 'recipe_name',
779 'hidden' : 0,
780 }
781 tc_recipeVersion = {
782 'name' : 'Recipe version',
783 'qhelp' : 'Version and revision of the recipe building the package',
784 'clclass' : 'recipe_version',
785 'hidden' : 1,
786 }
787 tc_layer = {
788 'name' : 'Layer',
789 'qhelp' : 'The name of the layer providing the recipe that builds the package',
790 'orderfield' : _get_toggle_order( request, "recipe__layer_version__layer__name" ),
791 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__layer__name" ),
792 'orderkey' : "recipe__layer_version__layer__name",
793 'clclass' : 'layer_name',
794 'hidden' : 1,
795 }
796 tc_layerBranch = {
797 'name' : 'Layer branch',
798 'qhelp' : 'The Git branch of the layer providing the recipe that builds the package',
799 'orderfield' : _get_toggle_order( request, "recipe__layer_version__branch" ),
800 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__branch" ),
801 'orderkey' : "recipe__layer_version__branch",
802 'clclass' : 'layer_branch',
803 'hidden' : 1,
804 }
805 tc_layerCommit = {
806 'name' : 'Layer commit',
807 'qhelp' : 'The Git commit of the layer providing the recipe that builds the package',
808 'clclass' : 'layer_commit',
809 'hidden' : 1,
810 }
811
812 context = {
813 'objectname': variant,
814 'build' : build,
815 'target' : Target.objects.filter( pk = target_id )[ 0 ],
816 'objects' : packages,
817 'packages_sum' : packages_sum[ 'installed_size__sum' ],
818 'object_search_display': "packages included",
819 'default_orderby' : orderby,
820 'tablecols' : [
821 tc_package,
822 tc_packageVersion,
823 tc_license,
824 tc_size,
825 tc_sizePercentage,
826 tc_dependencies,
827 tc_rdependencies,
828 tc_recipe,
829 tc_recipeVersion,
830 tc_layer,
831 tc_layerBranch,
832 tc_layerCommit,
833 ]
834 }
835
836
837 response = render(request, template, context)
838 _set_parameters_values(pagesize, orderby, request)
839 return response
840
841def target( request, build_id, target_id ):
842 return( target_common( request, build_id, target_id, "target" ))
843
844def targetpkg( request, build_id, target_id ):
845 return( target_common( request, build_id, target_id, "targetpkg" ))
846
847from django.core.serializers.json import DjangoJSONEncoder
848from django.http import HttpResponse
849def xhr_dirinfo(request, build_id, target_id):
850 top = request.GET.get('start', '/')
851 return HttpResponse(_get_dir_entries(build_id, target_id, top), content_type = "application/json")
852
853from django.utils.functional import Promise
854from django.utils.encoding import force_text
855class LazyEncoder(json.JSONEncoder):
856 def default(self, obj):
857 if isinstance(obj, Promise):
858 return force_text(obj)
859 return super(LazyEncoder, self).default(obj)
860
861from toastergui.templatetags.projecttags import filtered_filesizeformat
862import os
863def _get_dir_entries(build_id, target_id, start):
864 node_str = {
865 Target_File.ITYPE_REGULAR : '-',
866 Target_File.ITYPE_DIRECTORY : 'd',
867 Target_File.ITYPE_SYMLINK : 'l',
868 Target_File.ITYPE_SOCKET : 's',
869 Target_File.ITYPE_FIFO : 'p',
870 Target_File.ITYPE_CHARACTER : 'c',
871 Target_File.ITYPE_BLOCK : 'b',
872 }
873 response = []
874 objects = Target_File.objects.filter(target__exact=target_id, directory__path=start)
875 target_packages = Target_Installed_Package.objects.filter(target__exact=target_id).values_list('package_id', flat=True)
876 for o in objects:
877 # exclude root inode '/'
878 if o.path == '/':
879 continue
880 try:
881 entry = {}
882 entry['parent'] = start
883 entry['name'] = os.path.basename(o.path)
884 entry['fullpath'] = o.path
885
886 # set defaults, not all dentries have packages
887 entry['installed_package'] = None
888 entry['package_id'] = None
889 entry['package'] = None
890 entry['link_to'] = None
891 if o.inodetype == Target_File.ITYPE_DIRECTORY:
892 entry['isdir'] = 1
893 # is there content in directory
894 entry['childcount'] = Target_File.objects.filter(target__exact=target_id, directory__path=o.path).all().count()
895 else:
896 entry['isdir'] = 0
897
898 # resolve the file to get the package from the resolved file
899 resolved_id = o.sym_target_id
900 resolved_path = o.path
901 if target_packages.count():
902 while resolved_id != "" and resolved_id != None:
903 tf = Target_File.objects.get(pk=resolved_id)
904 resolved_path = tf.path
905 resolved_id = tf.sym_target_id
906
907 thisfile=Package_File.objects.all().filter(path__exact=resolved_path, package_id__in=target_packages)
908 if thisfile.count():
909 p = Package.objects.get(pk=thisfile[0].package_id)
910 entry['installed_package'] = p.installed_name
911 entry['package_id'] = str(p.id)
912 entry['package'] = p.name
913 # don't use resolved path from above, show immediate link-to
914 if o.sym_target_id != "" and o.sym_target_id != None:
915 entry['link_to'] = Target_File.objects.get(pk=o.sym_target_id).path
916 entry['size'] = filtered_filesizeformat(o.size)
917 if entry['link_to'] != None:
918 entry['permission'] = node_str[o.inodetype] + o.permission
919 else:
920 entry['permission'] = node_str[o.inodetype] + o.permission
921 entry['owner'] = o.owner
922 entry['group'] = o.group
923 response.append(entry)
924
925 except Exception as e:
926 print "Exception ", e
927 traceback.print_exc(e)
928
929 # sort by directories first, then by name
930 rsorted = sorted(response, key=lambda entry : entry['name'])
931 rsorted = sorted(rsorted, key=lambda entry : entry['isdir'], reverse=True)
932 return json.dumps(rsorted, cls=LazyEncoder).replace('</', '<\\/')
933
934def dirinfo(request, build_id, target_id, file_path=None):
935 template = "dirinfo.html"
936 objects = _get_dir_entries(build_id, target_id, '/')
937 packages_sum = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')).aggregate(Sum('installed_size'))
938 dir_list = None
939 if file_path != None:
940 """
941 Link from the included package detail file list page and is
942 requesting opening the dir info to a specific file path.
943 Provide the list of directories to expand and the full path to
944 highlight in the page.
945 """
946 # Aassume target's path separator matches host's, that is, os.sep
947 sep = os.sep
948 dir_list = []
949 head = file_path
950 while head != sep:
951 (head, tail) = os.path.split(head)
952 if head != sep:
953 dir_list.insert(0, head)
954
955 context = { 'build': Build.objects.get(pk=build_id),
956 'target': Target.objects.get(pk=target_id),
957 'packages_sum': packages_sum['installed_size__sum'],
958 'objects': objects,
959 'dir_list': dir_list,
960 'file_path': file_path,
961 }
962 return render(request, template, context)
963
964def _find_task_dep(task_object):
965 return map(lambda x: x.depends_on, Task_Dependency.objects.filter(task=task_object).filter(depends_on__order__gt = 0).exclude(depends_on__outcome = Task.OUTCOME_NA).select_related("depends_on"))
966
967
968def _find_task_revdep(task_object):
969 tp = []
970 tp = map(lambda t: t.task, Task_Dependency.objects.filter(depends_on=task_object).filter(task__order__gt=0).exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build"))
971 return tp
972
973def _find_task_revdep_list(tasklist):
974 tp = []
975 tp = map(lambda t: t.task, Task_Dependency.objects.filter(depends_on__in=tasklist).filter(task__order__gt=0).exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build"))
976 return tp
977
978def _find_task_provider(task_object):
979 task_revdeps = _find_task_revdep(task_object)
980 for tr in task_revdeps:
981 if tr.outcome != Task.OUTCOME_COVERED:
982 return tr
983 for tr in task_revdeps:
984 trc = _find_task_provider(tr)
985 if trc is not None:
986 return trc
987 return None
988
989def tasks_common(request, build_id, variant, task_anchor):
990# This class is shared between these pages
991#
992# Column tasks buildtime diskio cpuusage
993# --------- ------ ---------- ------- ---------
994# Cache def
995# CPU min -
996# Disk min -
997# Executed def def def def
998# Log
999# Order def +
1000# Outcome def def def def
1001# Recipe min min min min
1002# Version
1003# Task min min min min
1004# Time min -
1005#
1006# 'min':on always, 'def':on by default, else hidden
1007# '+' default column sort up, '-' default column sort down
1008
1009 anchor = request.GET.get('anchor', '')
1010 if not anchor:
1011 anchor=task_anchor
1012
1013 # default ordering depends on variant
1014 if 'buildtime' == variant:
1015 title_variant='Time'
1016 object_search_display="time data"
1017 filter_search_display="tasks"
1018 (pagesize, orderby) = _get_parameters_values(request, 25, 'elapsed_time:-')
1019 elif 'diskio' == variant:
1020 title_variant='Disk I/O'
1021 object_search_display="disk I/O data"
1022 filter_search_display="tasks"
1023 (pagesize, orderby) = _get_parameters_values(request, 25, 'disk_io:-')
1024 elif 'cpuusage' == variant:
1025 title_variant='CPU usage'
1026 object_search_display="CPU usage data"
1027 filter_search_display="tasks"
1028 (pagesize, orderby) = _get_parameters_values(request, 25, 'cpu_usage:-')
1029 else :
1030 title_variant='Tasks'
1031 object_search_display="tasks"
1032 filter_search_display="tasks"
1033 (pagesize, orderby) = _get_parameters_values(request, 25, 'order:+')
1034
1035
1036 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
1037
1038 template = 'tasks.html'
1039 retval = _verify_parameters( request.GET, mandatory_parameters )
1040 if retval:
1041 if task_anchor:
1042 mandatory_parameters['anchor']=task_anchor
1043 return _redirect_parameters( variant, request.GET, mandatory_parameters, build_id = build_id)
1044 (filter_string, search_term, ordering_string) = _search_tuple(request, Task)
1045 queryset_all = Task.objects.filter(build=build_id).exclude(order__isnull=True).exclude(outcome=Task.OUTCOME_NA)
1046 queryset_all = queryset_all.select_related("recipe", "build")
1047
1048 queryset_with_search = _get_queryset(Task, queryset_all, None , search_term, ordering_string, 'order')
1049
1050 if ordering_string.startswith('outcome'):
1051 queryset = _get_queryset(Task, queryset_all, filter_string, search_term, 'order:+', 'order')
1052 queryset = sorted(queryset, key=lambda ur: (ur.outcome_text), reverse=ordering_string.endswith('-'))
1053 elif ordering_string.startswith('sstate_result'):
1054 queryset = _get_queryset(Task, queryset_all, filter_string, search_term, 'order:+', 'order')
1055 queryset = sorted(queryset, key=lambda ur: (ur.sstate_text), reverse=ordering_string.endswith('-'))
1056 else:
1057 queryset = _get_queryset(Task, queryset_all, filter_string, search_term, ordering_string, 'order')
1058
1059
1060 # compute the anchor's page
1061 if anchor:
1062 request.GET = request.GET.copy()
1063 del request.GET['anchor']
1064 i=0
1065 a=int(anchor)
1066 count_per_page=int(pagesize)
1067 for task_object in queryset.iterator():
1068 if a == task_object.order:
1069 new_page= (i / count_per_page ) + 1
1070 request.GET.__setitem__('page', new_page)
1071 mandatory_parameters['page']=new_page
1072 return _redirect_parameters( variant, request.GET, mandatory_parameters, build_id = build_id)
1073 i += 1
1074
1075 task_objects = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
1076
1077 # define (and modify by variants) the 'tablecols' members
1078 tc_order={
1079 'name':'Order',
1080 'qhelp':'The running sequence of each task in the build',
1081 'clclass': 'order', 'hidden' : 1,
1082 'orderkey' : 'order',
1083 'orderfield':_get_toggle_order(request, "order"),
1084 'ordericon':_get_toggle_order_icon(request, "order")}
1085 if 'tasks' == variant:
1086 tc_order['hidden']='0'
1087 del tc_order['clclass']
1088
1089 tc_recipe={
1090 'name':'Recipe',
1091 'qhelp':'The name of the recipe to which each task applies',
1092 'orderkey' : 'recipe__name',
1093 'orderfield': _get_toggle_order(request, "recipe__name"),
1094 'ordericon':_get_toggle_order_icon(request, "recipe__name"),
1095 }
1096 tc_recipe_version={
1097 'name':'Recipe version',
1098 'qhelp':'The version of the recipe to which each task applies',
1099 'clclass': 'recipe_version', 'hidden' : 1,
1100 }
1101 tc_task={
1102 'name':'Task',
1103 'qhelp':'The name of the task',
1104 'orderfield': _get_toggle_order(request, "task_name"),
1105 'ordericon':_get_toggle_order_icon(request, "task_name"),
1106 'orderkey' : 'task_name',
1107 }
1108 tc_executed={
1109 'name':'Executed',
1110 'qhelp':"This value tells you if a task had to run (executed) in order to generate the task output, or if the output was provided by another task and therefore the task didn't need to run (not executed)",
1111 'clclass': 'executed', 'hidden' : 0,
1112 'orderfield': _get_toggle_order(request, "task_executed"),
1113 'ordericon':_get_toggle_order_icon(request, "task_executed"),
1114 'orderkey' : 'task_executed',
1115 'filter' : {
1116 'class' : 'executed',
1117 'label': 'Show:',
1118 'options' : [
1119 ('Executed Tasks', 'task_executed:1', queryset_with_search.filter(task_executed=1).count()),
1120 ('Not Executed Tasks', 'task_executed:0', queryset_with_search.filter(task_executed=0).count()),
1121 ]
1122 }
1123
1124 }
1125 tc_outcome={
1126 'name':'Outcome',
1127 'qhelp':"This column tells you if 'executed' tasks succeeded or failed. The column also tells you why 'not executed' tasks did not need to run",
1128 'clclass': 'outcome', 'hidden' : 0,
1129 'orderfield': _get_toggle_order(request, "outcome"),
1130 'ordericon':_get_toggle_order_icon(request, "outcome"),
1131 'orderkey' : 'outcome',
1132 'filter' : {
1133 'class' : 'outcome',
1134 'label': 'Show:',
1135 'options' : [
1136 ('Succeeded Tasks', 'outcome:%d'%Task.OUTCOME_SUCCESS, queryset_with_search.filter(outcome=Task.OUTCOME_SUCCESS).count(), "'Succeeded' tasks are those that ran and completed during the build" ),
1137 ('Failed Tasks', 'outcome:%d'%Task.OUTCOME_FAILED, queryset_with_search.filter(outcome=Task.OUTCOME_FAILED).count(), "'Failed' tasks are those that ran but did not complete during the build"),
1138 ('Cached Tasks', 'outcome:%d'%Task.OUTCOME_CACHED, queryset_with_search.filter(outcome=Task.OUTCOME_CACHED).count(), 'Cached tasks restore output from the <code>sstate-cache</code> directory or mirrors'),
1139 ('Prebuilt Tasks', 'outcome:%d'%Task.OUTCOME_PREBUILT, queryset_with_search.filter(outcome=Task.OUTCOME_PREBUILT).count(),'Prebuilt tasks didn\'t need to run because their output was reused from a previous build'),
1140 ('Covered Tasks', 'outcome:%d'%Task.OUTCOME_COVERED, queryset_with_search.filter(outcome=Task.OUTCOME_COVERED).count(), 'Covered tasks didn\'t need to run because their output is provided by another task in this build'),
1141 ('Empty Tasks', 'outcome:%d'%Task.OUTCOME_EMPTY, queryset_with_search.filter(outcome=Task.OUTCOME_EMPTY).count(), 'Empty tasks have no executable content'),
1142 ]
1143 }
1144
1145 }
1146
1147 tc_cache={
1148 'name':'Cache attempt',
1149 'qhelp':'This column tells you if a task tried to restore output from the <code>sstate-cache</code> directory or mirrors, and reports the result: Succeeded, Failed or File not in cache',
1150 'clclass': 'cache_attempt', 'hidden' : 0,
1151 'orderfield': _get_toggle_order(request, "sstate_result"),
1152 'ordericon':_get_toggle_order_icon(request, "sstate_result"),
1153 'orderkey' : 'sstate_result',
1154 'filter' : {
1155 'class' : 'cache_attempt',
1156 'label': 'Show:',
1157 'options' : [
1158 ('Tasks with cache attempts', 'sstate_result__gt:%d'%Task.SSTATE_NA, queryset_with_search.filter(sstate_result__gt=Task.SSTATE_NA).count(), 'Show all tasks that tried to restore ouput from the <code>sstate-cache</code> directory or mirrors'),
1159 ("Tasks with 'File not in cache' attempts", 'sstate_result:%d'%Task.SSTATE_MISS, queryset_with_search.filter(sstate_result=Task.SSTATE_MISS).count(), 'Show tasks that tried to restore output, but did not find it in the <code>sstate-cache</code> directory or mirrors'),
1160 ("Tasks with 'Failed' cache attempts", 'sstate_result:%d'%Task.SSTATE_FAILED, queryset_with_search.filter(sstate_result=Task.SSTATE_FAILED).count(), 'Show tasks that found the required output in the <code>sstate-cache</code> directory or mirrors, but could not restore it'),
1161 ("Tasks with 'Succeeded' cache attempts", 'sstate_result:%d'%Task.SSTATE_RESTORED, queryset_with_search.filter(sstate_result=Task.SSTATE_RESTORED).count(), 'Show tasks that successfully restored the required output from the <code>sstate-cache</code> directory or mirrors'),
1162 ]
1163 }
1164
1165 }
1166 #if 'tasks' == variant: tc_cache['hidden']='0';
1167 tc_time={
1168 'name':'Time (secs)',
1169 'qhelp':'How long it took the task to finish in seconds',
1170 'orderfield': _get_toggle_order(request, "elapsed_time", True),
1171 'ordericon':_get_toggle_order_icon(request, "elapsed_time"),
1172 'orderkey' : 'elapsed_time',
1173 'clclass': 'time_taken', 'hidden' : 1,
1174 }
1175 if 'buildtime' == variant:
1176 tc_time['hidden']='0'
1177 del tc_time['clclass']
1178 tc_cache['hidden']='1'
1179
1180 tc_cpu={
1181 'name':'CPU usage',
1182 'qhelp':'The percentage of task CPU utilization',
1183 'orderfield': _get_toggle_order(request, "cpu_usage", True),
1184 'ordericon':_get_toggle_order_icon(request, "cpu_usage"),
1185 'orderkey' : 'cpu_usage',
1186 'clclass': 'cpu_used', 'hidden' : 1,
1187 }
1188
1189 if 'cpuusage' == variant:
1190 tc_cpu['hidden']='0'
1191 del tc_cpu['clclass']
1192 tc_cache['hidden']='1'
1193
1194 tc_diskio={
1195 'name':'Disk I/O (ms)',
1196 'qhelp':'Number of miliseconds the task spent doing disk input and output',
1197 'orderfield': _get_toggle_order(request, "disk_io", True),
1198 'ordericon':_get_toggle_order_icon(request, "disk_io"),
1199 'orderkey' : 'disk_io',
1200 'clclass': 'disk_io', 'hidden' : 1,
1201 }
1202 if 'diskio' == variant:
1203 tc_diskio['hidden']='0'
1204 del tc_diskio['clclass']
1205 tc_cache['hidden']='1'
1206
1207 build = Build.objects.get(pk=build_id)
1208
1209 context = { 'objectname': variant,
1210 'object_search_display': object_search_display,
1211 'filter_search_display': filter_search_display,
1212 'title': title_variant,
1213 'build': build,
1214 'objects': task_objects,
1215 'default_orderby' : orderby,
1216 'search_term': search_term,
1217 'total_count': queryset_with_search.count(),
1218 'tablecols':[
1219 tc_order,
1220 tc_recipe,
1221 tc_recipe_version,
1222 tc_task,
1223 tc_executed,
1224 tc_outcome,
1225 tc_cache,
1226 tc_time,
1227 tc_cpu,
1228 tc_diskio,
1229 ]}
1230
1231
1232 response = render(request, template, context)
1233 _set_parameters_values(pagesize, orderby, request)
1234 return response
1235
1236def tasks(request, build_id):
1237 return tasks_common(request, build_id, 'tasks', '')
1238
1239def tasks_task(request, build_id, task_id):
1240 return tasks_common(request, build_id, 'tasks', task_id)
1241
1242def buildtime(request, build_id):
1243 return tasks_common(request, build_id, 'buildtime', '')
1244
1245def diskio(request, build_id):
1246 return tasks_common(request, build_id, 'diskio', '')
1247
1248def cpuusage(request, build_id):
1249 return tasks_common(request, build_id, 'cpuusage', '')
1250
1251
1252def recipes(request, build_id):
1253 template = 'recipes.html'
1254 (pagesize, orderby) = _get_parameters_values(request, 100, 'name:+')
1255 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1256 retval = _verify_parameters( request.GET, mandatory_parameters )
1257 if retval:
1258 return _redirect_parameters( 'recipes', request.GET, mandatory_parameters, build_id = build_id)
1259 (filter_string, search_term, ordering_string) = _search_tuple(request, Recipe)
1260 queryset = Recipe.objects.filter(layer_version__id__in=Layer_Version.objects.filter(build=build_id)).select_related("layer_version", "layer_version__layer")
1261 queryset = _get_queryset(Recipe, queryset, filter_string, search_term, ordering_string, 'name')
1262
1263 recipes = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
1264
1265 # prefetch the forward and reverse recipe dependencies
1266 deps = { }
1267 revs = { }
1268 queryset_dependency=Recipe_Dependency.objects.filter(recipe__layer_version__build_id = build_id).select_related("depends_on", "recipe")
1269 for recipe in recipes:
1270 deplist = [ ]
1271 for recipe_dep in [x for x in queryset_dependency if x.recipe_id == recipe.id]:
1272 deplist.append(recipe_dep)
1273 deps[recipe.id] = deplist
1274 revlist = [ ]
1275 for recipe_dep in [x for x in queryset_dependency if x.depends_on_id == recipe.id]:
1276 revlist.append(recipe_dep)
1277 revs[recipe.id] = revlist
1278
1279 build = Build.objects.get(pk=build_id)
1280
1281 context = {
1282 'objectname': 'recipes',
1283 'build': build,
1284 'objects': recipes,
1285 'default_orderby' : 'name:+',
1286 'recipe_deps' : deps,
1287 'recipe_revs' : revs,
1288 'tablecols':[
1289 {
1290 'name':'Recipe',
1291 'qhelp':'Information about a single piece of software, including where to download the source, configuration options, how to compile the source files and how to package the compiled output',
1292 'orderfield': _get_toggle_order(request, "name"),
1293 'ordericon':_get_toggle_order_icon(request, "name"),
1294 },
1295 {
1296 'name':'Recipe version',
1297 'qhelp':'The recipe version and revision',
1298 },
1299 {
1300 'name':'Dependencies',
1301 'qhelp':'Recipe build-time dependencies (i.e. other recipes)',
1302 'clclass': 'depends_on', 'hidden': 1,
1303 },
1304 {
1305 'name':'Reverse dependencies',
1306 'qhelp':'Recipe build-time reverse dependencies (i.e. the recipes that depend on this recipe)',
1307 'clclass': 'depends_by', 'hidden': 1,
1308 },
1309 {
1310 'name':'Recipe file',
1311 'qhelp':'Path to the recipe .bb file',
1312 'orderfield': _get_toggle_order(request, "file_path"),
1313 'ordericon':_get_toggle_order_icon(request, "file_path"),
1314 'orderkey' : 'file_path',
1315 'clclass': 'recipe_file', 'hidden': 0,
1316 },
1317 {
1318 'name':'Section',
1319 'qhelp':'The section in which recipes should be categorized',
1320 'orderfield': _get_toggle_order(request, "section"),
1321 'ordericon':_get_toggle_order_icon(request, "section"),
1322 'orderkey' : 'section',
1323 'clclass': 'recipe_section', 'hidden': 0,
1324 },
1325 {
1326 'name':'License',
1327 'qhelp':'The list of source licenses for the recipe. Multiple license names separated by the pipe character indicates a choice between licenses. Multiple license names separated by the ampersand character indicates multiple licenses exist that cover different parts of the source',
1328 'orderfield': _get_toggle_order(request, "license"),
1329 'ordericon':_get_toggle_order_icon(request, "license"),
1330 'orderkey' : 'license',
1331 'clclass': 'recipe_license', 'hidden': 0,
1332 },
1333 {
1334 'name':'Layer',
1335 'qhelp':'The name of the layer providing the recipe',
1336 'orderfield': _get_toggle_order(request, "layer_version__layer__name"),
1337 'ordericon':_get_toggle_order_icon(request, "layer_version__layer__name"),
1338 'orderkey' : 'layer_version__layer__name',
1339 'clclass': 'layer_version__layer__name', 'hidden': 0,
1340 },
1341 {
1342 'name':'Layer branch',
1343 'qhelp':'The Git branch of the layer providing the recipe',
1344 'orderfield': _get_toggle_order(request, "layer_version__branch"),
1345 'ordericon':_get_toggle_order_icon(request, "layer_version__branch"),
1346 'orderkey' : 'layer_version__branch',
1347 'clclass': 'layer_version__branch', 'hidden': 1,
1348 },
1349 {
1350 'name':'Layer commit',
1351 'qhelp':'The Git commit of the layer providing the recipe',
1352 'clclass': 'layer_version__layer__commit', 'hidden': 1,
1353 },
1354 ]
1355 }
1356
1357 response = render(request, template, context)
1358 _set_parameters_values(pagesize, orderby, request)
1359 return response
1360
1361def configuration(request, build_id):
1362 template = 'configuration.html'
1363
1364 var_names = ('BB_VERSION', 'BUILD_SYS', 'NATIVELSBSTRING', 'TARGET_SYS',
1365 'MACHINE', 'DISTRO', 'DISTRO_VERSION', 'TUNE_FEATURES', 'TARGET_FPU')
1366 context = dict(Variable.objects.filter(build=build_id, variable_name__in=var_names)\
1367 .values_list('variable_name', 'variable_value'))
1368 context.update({'objectname': 'configuration',
1369 'object_search_display':'variables',
1370 'filter_search_display':'variables',
1371 'build': Build.objects.get(pk=build_id),
1372 'targets': Target.objects.filter(build=build_id)})
1373 return render(request, template, context)
1374
1375
1376def configvars(request, build_id):
1377 template = 'configvars.html'
1378 (pagesize, orderby) = _get_parameters_values(request, 100, 'variable_name:+')
1379 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby, 'filter' : 'description__regex:.+' }
1380 retval = _verify_parameters( request.GET, mandatory_parameters )
1381 (filter_string, search_term, ordering_string) = _search_tuple(request, Variable)
1382 if retval:
1383 # if new search, clear the default filter
1384 if search_term and len(search_term):
1385 mandatory_parameters['filter']=''
1386 return _redirect_parameters( 'configvars', request.GET, mandatory_parameters, build_id = build_id)
1387
1388 queryset = Variable.objects.filter(build=build_id).exclude(variable_name__istartswith='B_').exclude(variable_name__istartswith='do_')
1389 queryset_with_search = _get_queryset(Variable, queryset, None, search_term, ordering_string, 'variable_name').exclude(variable_value='',vhistory__file_name__isnull=True)
1390 queryset = _get_queryset(Variable, queryset, filter_string, search_term, ordering_string, 'variable_name')
1391 # remove records where the value is empty AND there are no history files
1392 queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True)
1393
1394 variables = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
1395
1396 # show all matching files (not just the last one)
1397 file_filter= search_term + ":"
1398 if filter_string.find('/conf/') > 0:
1399 file_filter += 'conf/(local|bblayers).conf'
1400 if filter_string.find('conf/machine/') > 0:
1401 file_filter += 'conf/machine/'
1402 if filter_string.find('conf/distro/') > 0:
1403 file_filter += 'conf/distro/'
1404 if filter_string.find('/bitbake.conf') > 0:
1405 file_filter += '/bitbake.conf'
1406 build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path)
1407
1408 context = {
1409 'objectname': 'configvars',
1410 'object_search_display':'BitBake variables',
1411 'filter_search_display':'variables',
1412 'file_filter': file_filter,
1413 'build': Build.objects.get(pk=build_id),
1414 'objects' : variables,
1415 'total_count':queryset_with_search.count(),
1416 'default_orderby' : 'variable_name:+',
1417 'search_term':search_term,
1418 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
1419 'tablecols' : [
1420 {'name': 'Variable',
1421 'qhelp': "BitBake is a generic task executor that considers a list of tasks with dependencies and handles metadata that consists of variables in a certain format that get passed to the tasks",
1422 'orderfield': _get_toggle_order(request, "variable_name"),
1423 'ordericon':_get_toggle_order_icon(request, "variable_name"),
1424 },
1425 {'name': 'Value',
1426 'qhelp': "The value assigned to the variable",
1427 'dclass': "span4",
1428 },
1429 {'name': 'Set in file',
1430 'qhelp': "The last configuration file that touched the variable value",
1431 'clclass': 'file', 'hidden' : 0,
1432 'orderkey' : 'vhistory__file_name',
1433 'filter' : {
1434 'class' : 'vhistory__file_name',
1435 'label': 'Show:',
1436 'options' : [
1437 ('Local configuration variables', 'vhistory__file_name__contains:'+build_dir+'/conf/',queryset_with_search.filter(vhistory__file_name__contains=build_dir+'/conf/').count(), 'Select this filter to see variables set by the <code>local.conf</code> and <code>bblayers.conf</code> configuration files inside the <code>/build/conf/</code> directory'),
1438 ('Machine configuration variables', 'vhistory__file_name__contains:conf/machine/',queryset_with_search.filter(vhistory__file_name__contains='conf/machine').count(), 'Select this filter to see variables set by the configuration file(s) inside your layers <code>/conf/machine/</code> directory'),
1439 ('Distro configuration variables', 'vhistory__file_name__contains:conf/distro/',queryset_with_search.filter(vhistory__file_name__contains='conf/distro').count(), 'Select this filter to see variables set by the configuration file(s) inside your layers <code>/conf/distro/</code> directory'),
1440 ('Layer configuration variables', 'vhistory__file_name__contains:conf/layer.conf',queryset_with_search.filter(vhistory__file_name__contains='conf/layer.conf').count(), 'Select this filter to see variables set by the <code>layer.conf</code> configuration file inside your layers'),
1441 ('bitbake.conf variables', 'vhistory__file_name__contains:/bitbake.conf',queryset_with_search.filter(vhistory__file_name__contains='/bitbake.conf').count(), 'Select this filter to see variables set by the <code>bitbake.conf</code> configuration file'),
1442 ]
1443 },
1444 },
1445 {'name': 'Description',
1446 'qhelp': "A brief explanation of the variable",
1447 'clclass': 'description', 'hidden' : 0,
1448 'dclass': "span4",
1449 'filter' : {
1450 'class' : 'description',
1451 'label': 'Show:',
1452 'options' : [
1453 ('Variables with description', 'description__regex:.+', queryset_with_search.filter(description__regex='.+').count(), 'We provide descriptions for the most common BitBake variables. The list of descriptions lives in <code>meta/conf/documentation.conf</code>'),
1454 ]
1455 },
1456 },
1457 ],
1458 }
1459
1460 response = render(request, template, context)
1461 _set_parameters_values(pagesize, orderby, request)
1462 return response
1463
1464def bpackage(request, build_id):
1465 template = 'bpackage.html'
1466 (pagesize, orderby) = _get_parameters_values(request, 100, 'name:+')
1467 mandatory_parameters = { 'count' : pagesize, 'page' : 1, 'orderby' : orderby }
1468 retval = _verify_parameters( request.GET, mandatory_parameters )
1469 if retval:
1470 return _redirect_parameters( 'packages', request.GET, mandatory_parameters, build_id = build_id)
1471 (filter_string, search_term, ordering_string) = _search_tuple(request, Package)
1472 queryset = Package.objects.filter(build = build_id).filter(size__gte=0)
1473 queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name')
1474
1475 packages = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
1476
1477 build = Build.objects.get( pk = build_id )
1478
1479 context = {
1480 'objectname': 'packages built',
1481 'build': build,
1482 'objects' : packages,
1483 'default_orderby' : 'name:+',
1484 'tablecols':[
1485 {
1486 'name':'Package',
1487 'qhelp':'Packaged output resulting from building a recipe',
1488 'orderfield': _get_toggle_order(request, "name"),
1489 'ordericon':_get_toggle_order_icon(request, "name"),
1490 },
1491 {
1492 'name':'Package version',
1493 'qhelp':'The package version and revision',
1494 },
1495 {
1496 'name':'Size',
1497 'qhelp':'The size of the package',
1498 'orderfield': _get_toggle_order(request, "size", True),
1499 'ordericon':_get_toggle_order_icon(request, "size"),
1500 'orderkey' : 'size',
1501 'clclass': 'size', 'hidden': 0,
1502 'dclass' : 'span2',
1503 },
1504 {
1505 'name':'License',
1506 'qhelp':'The license under which the package is distributed. Multiple license names separated by the pipe character indicates a choice between licenses. Multiple license names separated by the ampersand character indicates multiple licenses exist that cover different parts of the source',
1507 'orderfield': _get_toggle_order(request, "license"),
1508 'ordericon':_get_toggle_order_icon(request, "license"),
1509 'orderkey' : 'license',
1510 'clclass': 'license', 'hidden': 1,
1511 },
1512 {
1513 'name':'Recipe',
1514 'qhelp':'The name of the recipe building the package',
1515 'orderfield': _get_toggle_order(request, "recipe__name"),
1516 'ordericon':_get_toggle_order_icon(request, "recipe__name"),
1517 'orderkey' : 'recipe__name',
1518 'clclass': 'recipe__name', 'hidden': 0,
1519 },
1520 {
1521 'name':'Recipe version',
1522 'qhelp':'Version and revision of the recipe building the package',
1523 'clclass': 'recipe__version', 'hidden': 1,
1524 },
1525 {
1526 'name':'Layer',
1527 'qhelp':'The name of the layer providing the recipe that builds the package',
1528 'orderfield': _get_toggle_order(request, "recipe__layer_version__layer__name"),
1529 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__layer__name"),
1530 'orderkey' : 'recipe__layer_version__layer__name',
1531 'clclass': 'recipe__layer_version__layer__name', 'hidden': 1,
1532 },
1533 {
1534 'name':'Layer branch',
1535 'qhelp':'The Git branch of the layer providing the recipe that builds the package',
1536 'orderfield': _get_toggle_order(request, "recipe__layer_version__branch"),
1537 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__branch"),
1538 'orderkey' : 'recipe__layer_version__branch',
1539 'clclass': 'recipe__layer_version__branch', 'hidden': 1,
1540 },
1541 {
1542 'name':'Layer commit',
1543 'qhelp':'The Git commit of the layer providing the recipe that builds the package',
1544 'clclass': 'recipe__layer_version__layer__commit', 'hidden': 1,
1545 },
1546 ]
1547 }
1548
1549 response = render(request, template, context)
1550 _set_parameters_values(pagesize, orderby, request)
1551 return response
1552
1553def bfile(request, build_id, package_id):
1554 template = 'bfile.html'
1555 files = Package_File.objects.filter(package = package_id)
1556 context = {'build': Build.objects.get(pk=build_id), 'objects' : files}
1557 return render(request, template, context)
1558
1559
1560# A set of dependency types valid for both included and built package views
1561OTHER_DEPENDS_BASE = [
1562 Package_Dependency.TYPE_RSUGGESTS,
1563 Package_Dependency.TYPE_RPROVIDES,
1564 Package_Dependency.TYPE_RREPLACES,
1565 Package_Dependency.TYPE_RCONFLICTS,
1566 ]
1567
1568# value for invalid row id
1569INVALID_KEY = -1
1570
1571"""
1572Given a package id, target_id retrieves two sets of this image and package's
1573dependencies. The return value is a dictionary consisting of two other
1574lists: a list of 'runtime' dependencies, that is, having RDEPENDS
1575values in source package's recipe, and a list of other dependencies, that is
1576the list of possible recipe variables as found in OTHER_DEPENDS_BASE plus
1577the RRECOMMENDS or TRECOMMENDS value.
1578The lists are built in the sort order specified for the package runtime
1579dependency views.
1580"""
1581def _get_package_dependencies(package_id, target_id = INVALID_KEY):
1582 runtime_deps = []
1583 other_deps = []
1584 other_depends_types = OTHER_DEPENDS_BASE
1585
1586 if target_id != INVALID_KEY :
1587 rdepends_type = Package_Dependency.TYPE_TRDEPENDS
1588 other_depends_types += [Package_Dependency.TYPE_TRECOMMENDS]
1589 else :
1590 rdepends_type = Package_Dependency.TYPE_RDEPENDS
1591 other_depends_types += [Package_Dependency.TYPE_RRECOMMENDS]
1592
1593 package = Package.objects.get(pk=package_id)
1594 if target_id != INVALID_KEY :
1595 alldeps = package.package_dependencies_source.filter(target_id__exact = target_id)
1596 else :
1597 alldeps = package.package_dependencies_source.all()
1598 for idep in alldeps:
1599 dep_package = Package.objects.get(pk=idep.depends_on_id)
1600 dep_entry = Package_Dependency.DEPENDS_DICT[idep.dep_type]
1601 if dep_package.version == '' :
1602 version = ''
1603 else :
1604 version = dep_package.version + "-" + dep_package.revision
1605 installed = False
1606 if target_id != INVALID_KEY :
1607 if Target_Installed_Package.objects.filter(target_id__exact = target_id, package_id__exact = dep_package.id).count() > 0:
1608 installed = True
1609 dep = {
1610 'name' : dep_package.name,
1611 'version' : version,
1612 'size' : dep_package.size,
1613 'dep_type' : idep.dep_type,
1614 'dep_type_display' : dep_entry[0].capitalize(),
1615 'dep_type_help' : dep_entry[1] % (dep_package.name, package.name),
1616 'depends_on_id' : dep_package.id,
1617 'installed' : installed,
1618 }
1619
1620 if target_id != INVALID_KEY:
1621 dep['alias'] = _get_package_alias(dep_package)
1622
1623 if idep.dep_type == rdepends_type :
1624 runtime_deps.append(dep)
1625 elif idep.dep_type in other_depends_types :
1626 other_deps.append(dep)
1627
1628 rdep_sorted = sorted(runtime_deps, key=lambda k: k['name'])
1629 odep_sorted = sorted(
1630 sorted(other_deps, key=lambda k: k['name']),
1631 key=lambda k: k['dep_type'])
1632 retvalues = {'runtime_deps' : rdep_sorted, 'other_deps' : odep_sorted}
1633 return retvalues
1634
1635# Return the count of packages dependent on package for this target_id image
1636def _get_package_reverse_dep_count(package, target_id):
1637 return package.package_dependencies_target.filter(target_id__exact=target_id, dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1638
1639# Return the count of the packages that this package_id is dependent on.
1640# Use one of the two RDEPENDS types, either TRDEPENDS if the package was
1641# installed, or else RDEPENDS if only built.
1642def _get_package_dependency_count(package, target_id, is_installed):
1643 if is_installed :
1644 return package.package_dependencies_source.filter(target_id__exact = target_id,
1645 dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1646 else :
1647 return package.package_dependencies_source.filter(dep_type__exact = Package_Dependency.TYPE_RDEPENDS).count()
1648
1649def _get_package_alias(package):
1650 alias = package.installed_name
1651 if alias != None and alias != '' and alias != package.name:
1652 return alias
1653 else:
1654 return ''
1655
1656def _get_fullpackagespec(package):
1657 r = package.name
1658 version_good = package.version != None and package.version != ''
1659 revision_good = package.revision != None and package.revision != ''
1660 if version_good or revision_good:
1661 r += '_'
1662 if version_good:
1663 r += package.version
1664 if revision_good:
1665 r += '-'
1666 if revision_good:
1667 r += package.revision
1668 return r
1669
1670def package_built_detail(request, build_id, package_id):
1671 template = "package_built_detail.html"
1672 if Build.objects.filter(pk=build_id).count() == 0 :
1673 return redirect(builds)
1674
1675 # follow convention for pagination w/ search although not used for this view
1676 queryset = Package_File.objects.filter(package_id__exact=package_id)
1677 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1678 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1679 retval = _verify_parameters( request.GET, mandatory_parameters )
1680 if retval:
1681 return _redirect_parameters( 'package_built_detail', request.GET, mandatory_parameters, build_id = build_id, package_id = package_id)
1682
1683 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1684 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1685
1686 package = Package.objects.get(pk=package_id)
1687 package.fullpackagespec = _get_fullpackagespec(package)
1688 context = {
1689 'build' : Build.objects.get(pk=build_id),
1690 'package' : package,
1691 'dependency_count' : _get_package_dependency_count(package, -1, False),
1692 'objects' : paths,
1693 'tablecols':[
1694 {
1695 'name':'File',
1696 'orderfield': _get_toggle_order(request, "path"),
1697 'ordericon':_get_toggle_order_icon(request, "path"),
1698 },
1699 {
1700 'name':'Size',
1701 'orderfield': _get_toggle_order(request, "size", True),
1702 'ordericon':_get_toggle_order_icon(request, "size"),
1703 'dclass': 'sizecol span2',
1704 },
1705 ]
1706 }
1707 if paths.all().count() < 2:
1708 context['disable_sort'] = True;
1709
1710 response = render(request, template, context)
1711 _set_parameters_values(pagesize, orderby, request)
1712 return response
1713
1714def package_built_dependencies(request, build_id, package_id):
1715 template = "package_built_dependencies.html"
1716 if Build.objects.filter(pk=build_id).count() == 0 :
1717 return redirect(builds)
1718
1719 package = Package.objects.get(pk=package_id)
1720 package.fullpackagespec = _get_fullpackagespec(package)
1721 dependencies = _get_package_dependencies(package_id)
1722 context = {
1723 'build' : Build.objects.get(pk=build_id),
1724 'package' : package,
1725 'runtime_deps' : dependencies['runtime_deps'],
1726 'other_deps' : dependencies['other_deps'],
1727 'dependency_count' : _get_package_dependency_count(package, -1, False)
1728 }
1729 return render(request, template, context)
1730
1731
1732def package_included_detail(request, build_id, target_id, package_id):
1733 template = "package_included_detail.html"
1734 if Build.objects.filter(pk=build_id).count() == 0 :
1735 return redirect(builds)
1736
1737 # follow convention for pagination w/ search although not used for this view
1738 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1739 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1740 retval = _verify_parameters( request.GET, mandatory_parameters )
1741 if retval:
1742 return _redirect_parameters( 'package_included_detail', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1743 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1744
1745 queryset = Package_File.objects.filter(package_id__exact=package_id)
1746 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1747
1748 package = Package.objects.get(pk=package_id)
1749 package.fullpackagespec = _get_fullpackagespec(package)
1750 package.alias = _get_package_alias(package)
1751 target = Target.objects.get(pk=target_id)
1752 context = {
1753 'build' : Build.objects.get(pk=build_id),
1754 'target' : target,
1755 'package' : package,
1756 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1757 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1758 'objects': paths,
1759 'tablecols':[
1760 {
1761 'name':'File',
1762 'orderfield': _get_toggle_order(request, "path"),
1763 'ordericon':_get_toggle_order_icon(request, "path"),
1764 },
1765 {
1766 'name':'Size',
1767 'orderfield': _get_toggle_order(request, "size", True),
1768 'ordericon':_get_toggle_order_icon(request, "size"),
1769 'dclass': 'sizecol span2',
1770 },
1771 ]
1772 }
1773 if paths.all().count() < 2:
1774 context['disable_sort'] = True
1775 response = render(request, template, context)
1776 _set_parameters_values(pagesize, orderby, request)
1777 return response
1778
1779def package_included_dependencies(request, build_id, target_id, package_id):
1780 template = "package_included_dependencies.html"
1781 if Build.objects.filter(pk=build_id).count() == 0 :
1782 return redirect(builds)
1783
1784 package = Package.objects.get(pk=package_id)
1785 package.fullpackagespec = _get_fullpackagespec(package)
1786 package.alias = _get_package_alias(package)
1787 target = Target.objects.get(pk=target_id)
1788
1789 dependencies = _get_package_dependencies(package_id, target_id)
1790 context = {
1791 'build' : Build.objects.get(pk=build_id),
1792 'package' : package,
1793 'target' : target,
1794 'runtime_deps' : dependencies['runtime_deps'],
1795 'other_deps' : dependencies['other_deps'],
1796 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1797 'dependency_count' : _get_package_dependency_count(package, target_id, True)
1798 }
1799 return render(request, template, context)
1800
1801def package_included_reverse_dependencies(request, build_id, target_id, package_id):
1802 template = "package_included_reverse_dependencies.html"
1803 if Build.objects.filter(pk=build_id).count() == 0 :
1804 return redirect(builds)
1805
1806 (pagesize, orderby) = _get_parameters_values(request, 25, 'package__name:+')
1807 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
1808 retval = _verify_parameters( request.GET, mandatory_parameters )
1809 if retval:
1810 return _redirect_parameters( 'package_included_reverse_dependencies', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1811 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1812
1813 queryset = Package_Dependency.objects.select_related('depends_on__name', 'depends_on__size').filter(depends_on=package_id, target_id=target_id, dep_type=Package_Dependency.TYPE_TRDEPENDS)
1814 objects = _get_queryset(Package_Dependency, queryset, filter_string, search_term, ordering_string, 'package__name')
1815
1816 package = Package.objects.get(pk=package_id)
1817 package.fullpackagespec = _get_fullpackagespec(package)
1818 package.alias = _get_package_alias(package)
1819 target = Target.objects.get(pk=target_id)
1820 for o in objects:
1821 if o.package.version != '':
1822 o.package.version += '-' + o.package.revision
1823 o.alias = _get_package_alias(o.package)
1824 context = {
1825 'build' : Build.objects.get(pk=build_id),
1826 'package' : package,
1827 'target' : target,
1828 'objects' : objects,
1829 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1830 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1831 'tablecols':[
1832 {
1833 'name':'Package',
1834 'orderfield': _get_toggle_order(request, "package__name"),
1835 'ordericon': _get_toggle_order_icon(request, "package__name"),
1836 },
1837 {
1838 'name':'Version',
1839 },
1840 {
1841 'name':'Size',
1842 'orderfield': _get_toggle_order(request, "package__size", True),
1843 'ordericon': _get_toggle_order_icon(request, "package__size"),
1844 'dclass': 'sizecol span2',
1845 },
1846 ]
1847 }
1848 if objects.all().count() < 2:
1849 context['disable_sort'] = True
1850 response = render(request, template, context)
1851 _set_parameters_values(pagesize, orderby, request)
1852 return response
1853
1854def image_information_dir(request, build_id, target_id, packagefile_id):
1855 # stubbed for now
1856 return redirect(builds)
1857 # the context processor that supplies data used across all the pages
1858
1859
1860def managedcontextprocessor(request):
1861 ret = {
1862 "projects": Project.objects.all(),
1863 "DEBUG" : toastermain.settings.DEBUG,
1864 "TOASTER_BRANCH": toastermain.settings.TOASTER_BRANCH,
1865 "TOASTER_REVISION" : toastermain.settings.TOASTER_REVISION,
1866 }
1867 return ret
1868
1869
1870
1871import toastermain.settings
1872
1873from orm.models import Project, ProjectLayer, ProjectTarget, ProjectVariable
1874
1875# we have a set of functions if we're in managed mode, or
1876# a default "page not available" simple functions for interactive mode
1877
1878if True:
1879 from django.contrib.auth.models import User
1880 from django.contrib.auth import authenticate, login
1881 from django.contrib.auth.decorators import login_required
1882
1883 from orm.models import Branch, LayerSource, ToasterSetting, Release, Machine, LayerVersionDependency
1884 from bldcontrol.models import BuildRequest
1885
1886 import traceback
1887
1888 class BadParameterException(Exception):
1889 ''' The exception raised on invalid POST requests '''
1890 pass
1891
1892 # shows the "all builds" page for managed mode; it displays build requests (at least started!) instead of actual builds
1893 @_template_renderer("builds.html")
1894 def builds(request):
1895 # define here what parameters the view needs in the GET portion in order to
1896 # be able to display something. 'count' and 'page' are mandatory for all views
1897 # that use paginators.
1898
1899 queryset = Build.objects.exclude(outcome = Build.IN_PROGRESS)
1900
1901 try:
1902 context, pagesize, orderby = _build_list_helper(request, queryset)
1903 # all builds page as a Project column
1904 context['tablecols'].append({'name': 'Project', 'clcalss': 'project_column', })
1905 except RedirectException as re:
1906 # rewrite the RedirectException
1907 re.view = resolve(request.path_info).url_name
1908 raise re
1909
1910 _set_parameters_values(pagesize, orderby, request)
1911 return context
1912
1913
1914 # helper function, to be used on "all builds" and "project builds" pages
1915 def _build_list_helper(request, queryset_all):
1916
1917 default_orderby = 'completed_on:-'
1918 (pagesize, orderby) = _get_parameters_values(request, 10, default_orderby)
1919 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1920 retval = _verify_parameters( request.GET, mandatory_parameters )
1921 if retval:
1922 raise RedirectException( None, request.GET, mandatory_parameters)
1923
1924 # boilerplate code that takes a request for an object type and returns a queryset
1925 # for that object type. copypasta for all needed table searches
1926 (filter_string, search_term, ordering_string) = _search_tuple(request, Build)
1927 # post-process any date range filters
1928 filter_string,daterange_selected = _modify_date_range_filter(filter_string)
1929 queryset_all = queryset_all.select_related("project").annotate(errors_no = Count('logmessage', only=Q(logmessage__level=LogMessage.ERROR)|Q(logmessage__level=LogMessage.EXCEPTION))).annotate(warnings_no = Count('logmessage', only=Q(logmessage__level=LogMessage.WARNING))).extra(select={'timespent':'completed_on - started_on'})
1930 queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on')
1931 queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on')
1932
1933 # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display
1934 build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
1935
1936 # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
1937 build_mru = Build.objects.order_by("-started_on")[:3]
1938
1939 # calculate the exact begining of local today and yesterday, append context
1940 context_date,today_begin,yesterday_begin = _add_daterange_context(queryset_all, request, {'started_on','completed_on'})
1941
1942 # set up list of fstypes for each build
1943 fstypes_map = {};
1944 for build in build_info:
1945 targets = Target.objects.filter( build_id = build.id )
1946 comma = "";
1947 extensions = "";
1948 for t in targets:
1949 if ( not t.is_image ):
1950 continue
1951 tif = Target_Image_File.objects.filter( target_id = t.id )
1952 for i in tif:
1953 s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
1954 if s == i.file_name:
1955 s=re.sub('.*\.', '', i.file_name)
1956 if None == re.search(s,extensions):
1957 extensions += comma + s
1958 comma = ", "
1959 fstypes_map[build.id]=extensions
1960
1961 # send the data to the template
1962 context = {
1963 # specific info for
1964 'mru' : build_mru,
1965 # TODO: common objects for all table views, adapt as needed
1966 'objects' : build_info,
1967 'objectname' : "builds",
1968 'default_orderby' : default_orderby,
1969 'fstypes' : fstypes_map,
1970 'search_term' : search_term,
1971 'total_count' : queryset_with_search.count(),
1972 'daterange_selected' : daterange_selected,
1973 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
1974 'tablecols' : [
1975 {'name': 'Outcome', # column with a single filter
1976 'qhelp' : "The outcome tells you if a build successfully completed or failed", # the help button content
1977 'dclass' : "span2", # indication about column width; comes from the design
1978 'orderfield': _get_toggle_order(request, "outcome"), # adds ordering by the field value; default ascending unless clicked from ascending into descending
1979 'ordericon':_get_toggle_order_icon(request, "outcome"),
1980 # filter field will set a filter on that column with the specs in the filter description
1981 # the class field in the filter has no relation with clclass; the control different aspects of the UI
1982 # still, it is recommended for the values to be identical for easy tracking in the generated HTML
1983 'filter' : {'class' : 'outcome',
1984 'label': 'Show:',
1985 'options' : [
1986 ('Successful builds', 'outcome:' + str(Build.SUCCEEDED), queryset_with_search.filter(outcome=str(Build.SUCCEEDED)).count()), # this is the field search expression
1987 ('Failed builds', 'outcome:'+ str(Build.FAILED), queryset_with_search.filter(outcome=str(Build.FAILED)).count()),
1988 ]
1989 }
1990 },
1991 {'name': 'Recipe', # default column, disabled box, with just the name in the list
1992 'qhelp': "What you built (i.e. one or more recipes or image recipes)",
1993 'orderfield': _get_toggle_order(request, "target__target"),
1994 'ordericon':_get_toggle_order_icon(request, "target__target"),
1995 },
1996 {'name': 'Machine',
1997 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe",
1998 'orderfield': _get_toggle_order(request, "machine"),
1999 'ordericon':_get_toggle_order_icon(request, "machine"),
2000 'dclass': 'span3'
2001 }, # a slightly wider column
2002 {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column
2003 'qhelp': "The date and time you started the build",
2004 'orderfield': _get_toggle_order(request, "started_on", True),
2005 'ordericon':_get_toggle_order_icon(request, "started_on"),
2006 'orderkey' : "started_on",
2007 'filter' : {'class' : 'started_on',
2008 'label': 'Show:',
2009 'options' : [
2010 ("Today's builds" , 'started_on__gte:'+today_begin.strftime("%Y-%m-%d"), queryset_all.filter(started_on__gte=today_begin).count()),
2011 ("Yesterday's builds",
2012 'started_on__gte!started_on__lt:'
2013 +yesterday_begin.strftime("%Y-%m-%d")+'!'
2014 +today_begin.strftime("%Y-%m-%d"),
2015 queryset_all.filter(
2016 started_on__gte=yesterday_begin,
2017 started_on__lt=today_begin
2018 ).count()),
2019 ("Build date range", 'daterange', 1, '', 'started_on'),
2020 ]
2021 }
2022 },
2023 {'name': 'Completed on',
2024 'qhelp': "The date and time the build finished",
2025 'orderfield': _get_toggle_order(request, "completed_on", True),
2026 'ordericon':_get_toggle_order_icon(request, "completed_on"),
2027 'orderkey' : 'completed_on',
2028 'filter' : {'class' : 'completed_on',
2029 'label': 'Show:',
2030 'options' : [
2031 ("Today's builds" , 'completed_on__gte:'+today_begin.strftime("%Y-%m-%d"), queryset_all.filter(completed_on__gte=today_begin).count()),
2032 ("Yesterday's builds",
2033 'completed_on__gte!completed_on__lt:'
2034 +yesterday_begin.strftime("%Y-%m-%d")+'!'
2035 +today_begin.strftime("%Y-%m-%d"),
2036 queryset_all.filter(
2037 completed_on__gte=yesterday_begin,
2038 completed_on__lt=today_begin
2039 ).count()),
2040 ("Build date range", 'daterange', 1, '', 'completed_on'),
2041 ]
2042 }
2043 },
2044 {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox
2045 'qhelp': "How many tasks failed during the build",
2046 'filter' : {'class' : 'failed_tasks',
2047 'label': 'Show:',
2048 'options' : [
2049 ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()),
2050 ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()),
2051 ]
2052 }
2053 },
2054 {'name': 'Errors', 'clclass': 'errors_no',
2055 'qhelp': "How many errors were encountered during the build (if any)",
2056 'orderfield': _get_toggle_order(request, "errors_no", True),
2057 'ordericon':_get_toggle_order_icon(request, "errors_no"),
2058 'orderkey' : 'errors_no',
2059 'filter' : {'class' : 'errors_no',
2060 'label': 'Show:',
2061 'options' : [
2062 ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()),
2063 ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()),
2064 ]
2065 }
2066 },
2067 {'name': 'Warnings', 'clclass': 'warnings_no',
2068 'qhelp': "How many warnings were encountered during the build (if any)",
2069 'orderfield': _get_toggle_order(request, "warnings_no", True),
2070 'ordericon':_get_toggle_order_icon(request, "warnings_no"),
2071 'orderkey' : 'warnings_no',
2072 'filter' : {'class' : 'warnings_no',
2073 'label': 'Show:',
2074 'options' : [
2075 ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()),
2076 ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()),
2077 ]
2078 }
2079 },
2080 {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
2081 'qhelp': "How long it took the build to finish",
2082 'orderfield': _get_toggle_order(request, "timespent", True),
2083 'ordericon':_get_toggle_order_icon(request, "timespent"),
2084 'orderkey' : 'timespent',
2085 },
2086 {'name': 'Image files', 'clclass': 'output',
2087 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
2088 # TODO: compute image fstypes from Target_Image_File
2089 }
2090 ]
2091 }
2092
2093 # merge daterange values
2094 context.update(context_date)
2095 return context, pagesize, orderby
2096
2097
2098
2099 # new project
2100 def newproject(request):
2101 template = "newproject.html"
2102 context = {
2103 'email': request.user.email if request.user.is_authenticated() else '',
2104 'username': request.user.username if request.user.is_authenticated() else '',
2105 'releases': Release.objects.order_by("description"),
2106 }
2107
2108 try:
2109 context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
2110 except ToasterSetting.DoesNotExist:
2111 pass
2112
2113 if request.method == "GET":
2114 # render new project page
2115 return render(request, template, context)
2116 elif request.method == "POST":
2117 mandatory_fields = ['projectname', 'ptype']
2118 try:
2119 ptype = request.POST.get('ptype')
2120 if ptype == "build":
2121 mandatory_fields.append('projectversion')
2122 # make sure we have values for all mandatory_fields
2123 if reduce( lambda x, y: x or y, map(lambda x: len(request.POST.get(x, '')) == 0, mandatory_fields)):
2124 # set alert for missing fields
2125 raise BadParameterException("Fields missing: " +
2126 ", ".join([x for x in mandatory_fields if len(request.POST.get(x, '')) == 0 ]))
2127
2128 if not request.user.is_authenticated():
2129 user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
2130 if user is None:
2131 user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
2132
2133 user = authenticate(username = user.username, password = 'nopass')
2134 login(request, user)
2135
2136 # save the project
2137 if ptype == "analysis":
2138 release = None
2139 else:
2140 release = Release.objects.get(pk = request.POST.get('projectversion', None ))
2141
2142 prj = Project.objects.create_project(name = request.POST['projectname'], release = release)
2143 prj.user_id = request.user.pk
2144 prj.save()
2145 return redirect(reverse(project, args=(prj.pk,)) + "?notify=new-project")
2146
2147 except (IntegrityError, BadParameterException) as e:
2148 # fill in page with previously submitted values
2149 map(lambda x: context.__setitem__(x, request.POST.get(x, "-- missing")), mandatory_fields)
2150 if isinstance(e, IntegrityError) and "username" in str(e):
2151 context['alert'] = "Your chosen username is already used"
2152 else:
2153 context['alert'] = str(e)
2154 return render(request, template, context)
2155
2156 raise Exception("Invalid HTTP method for this page")
2157
2158
2159
2160 # Shows the edit project page
2161 @_template_renderer('project.html')
2162 def project(request, pid):
2163 prj = Project.objects.get(id = pid)
2164
2165 try:
2166 puser = User.objects.get(id = prj.user_id)
2167 except User.DoesNotExist:
2168 puser = None
2169
2170 # execute POST requests
2171 if request.method == "POST":
2172 # add layers
2173 if 'layerAdd' in request.POST and len(request.POST['layerAdd']) > 0:
2174 for lc in Layer_Version.objects.filter(pk__in=[i for i in request.POST['layerAdd'].split(",") if len(i) > 0]):
2175 ProjectLayer.objects.get_or_create(project = prj, layercommit = lc)
2176
2177 # remove layers
2178 if 'layerDel' in request.POST and len(request.POST['layerDel']) > 0:
2179 for t in request.POST['layerDel'].strip().split(" "):
2180 pt = ProjectLayer.objects.filter(project = prj, layercommit_id = int(t)).delete()
2181
2182 if 'projectName' in request.POST:
2183 prj.name = request.POST['projectName']
2184 prj.save();
2185
2186 if 'projectVersion' in request.POST:
2187 # If the release is the current project then return now
2188 if prj.release.pk == int(request.POST.get('projectVersion',-1)):
2189 return {}
2190
2191 prj.release = Release.objects.get(pk = request.POST['projectVersion'])
2192 # we need to change the bitbake version
2193 prj.bitbake_version = prj.release.bitbake_version
2194 prj.save()
2195 # we need to change the layers
2196 for i in prj.projectlayer_set.all():
2197 # find and add a similarly-named layer on the new branch
2198 try:
2199 lv = prj.compatible_layerversions(layer_name = i.layercommit.layer.name)[0]
2200 ProjectLayer.objects.get_or_create(project = prj, layercommit = lv)
2201 except IndexError:
2202 pass
2203 finally:
2204 # get rid of the old entry
2205 i.delete()
2206
2207 if 'machineName' in request.POST:
2208 machinevar = prj.projectvariable_set.get(name="MACHINE")
2209 machinevar.value=request.POST['machineName']
2210 machinevar.save()
2211
2212
2213 # we use implicit knowledge of the current user's project to filter layer information, e.g.
2214 pid = prj.id
2215
2216 from collections import Counter
2217 freqtargets = []
2218 try:
2219 freqtargets += map(lambda x: x.target, reduce(lambda x, y: x + y, map(lambda x: list(x.target_set.all()), Build.objects.filter(project = prj, outcome__lt = Build.IN_PROGRESS))))
2220 freqtargets += map(lambda x: x.target, reduce(lambda x, y: x + y, map(lambda x: list(x.brtarget_set.all()), BuildRequest.objects.filter(project = prj, state = BuildRequest.REQ_FAILED))))
2221 except TypeError:
2222 pass
2223 freqtargets = Counter(freqtargets)
2224 freqtargets = sorted(freqtargets, key = lambda x: freqtargets[x], reverse=True)
2225
2226 context = {
2227 "project" : prj,
2228 "lvs_nos" : Layer_Version.objects.all().count(),
2229 "completedbuilds": Build.objects.filter(project_id = pid).filter(outcome__lte = Build.IN_PROGRESS),
2230 "prj" : {"name": prj.name, },
2231 "buildrequests" : prj.build_set.filter(outcome=Build.IN_PROGRESS),
2232 "builds" : _project_recent_build_list(prj),
2233 "layers" : map(lambda x: {
2234 "id": x.layercommit.pk,
2235 "orderid": x.pk,
2236 "name" : x.layercommit.layer.name,
2237 "vcs_url": x.layercommit.layer.vcs_url,
2238 "vcs_reference" : x.layercommit.get_vcs_reference(),
2239 "url": x.layercommit.layer.layer_index_url,
2240 "layerdetailurl": x.layercommit.get_detailspage_url(prj.pk),
2241 # This branch name is actually the release
2242 "branch" : { "name" : x.layercommit.get_vcs_reference(), "layersource" : x.layercommit.up_branch.layer_source.name if x.layercommit.up_branch != None else None}},
2243 prj.projectlayer_set.all().order_by("id")),
2244 "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()),
2245 "variables": map(lambda x: (x.name, x.value), prj.projectvariable_set.all()),
2246 "freqtargets": freqtargets[:5],
2247 "releases": map(lambda x: {"id": x.pk, "name": x.name, "description":x.description}, Release.objects.all()),
2248 "project_html": 1,
2249 "recipesTypeAheadUrl": reverse('xhr_recipestypeahead', args=(prj.pk,)),
2250 "projectBuildsUrl": reverse('projectbuilds', args=(prj.pk,)),
2251 }
2252
2253 if prj.release is not None:
2254 context['release'] = { "id": prj.release.pk, "name": prj.release.name, "description": prj.release.description}
2255
2256
2257 try:
2258 context["machine"] = {"name": prj.projectvariable_set.get(name="MACHINE").value}
2259 except ProjectVariable.DoesNotExist:
2260 context["machine"] = None
2261 try:
2262 context["distro"] = prj.projectvariable_set.get(name="DISTRO").value
2263 except ProjectVariable.DoesNotExist:
2264 context["distro"] = "-- not set yet"
2265
2266 return context
2267
2268 def jsunittests(request):
2269 """ Provides a page for the js unit tests """
2270 bbv = BitbakeVersion.objects.filter(branch="master").first()
2271 release = Release.objects.filter(bitbake_version=bbv).first()
2272
2273 name = "_js_unit_test_prj_"
2274
2275 # If there is an existing project by this name delete it. We don't want
2276 # Lots of duplicates cluttering up the projects.
2277 Project.objects.filter(name=name).delete()
2278
2279 new_project = Project.objects.create_project(name=name, release=release)
2280
2281 context = { 'project' : new_project }
2282 return render(request, "js-unit-tests.html", context)
2283
2284 from django.views.decorators.csrf import csrf_exempt
2285 @csrf_exempt
2286 def xhr_testreleasechange(request, pid):
2287 def response(data):
2288 return HttpResponse(jsonfilter(data),
2289 content_type="application/json")
2290
2291 """ returns layer versions that would be deleted on the new
2292 release__pk """
2293 try:
2294 prj = Project.objects.get(pk = pid)
2295 new_release_id = request.GET['new_release_id']
2296
2297 # If we're already on this project do nothing
2298 if prj.release.pk == int(new_release_id):
2299 return reponse({"error": "ok", "rows": []})
2300
2301 retval = []
2302
2303 for i in prj.projectlayer_set.all():
2304 lv = prj.compatible_layerversions(release = Release.objects.get(pk=new_release_id)).filter(layer__name = i.layercommit.layer.name)
2305 # there is no layer_version with the new release id,
2306 # and the same name
2307 if lv.count() < 1:
2308 retval.append(i)
2309
2310 return response({"error":"ok",
2311 "rows" : map( _lv_to_dict(prj),
2312 map(lambda x: x.layercommit, retval ))
2313 })
2314
2315 except Exception as e:
2316 return response({"error": str(e) })
2317
2318 def xhr_configvaredit(request, pid):
2319 try:
2320 prj = Project.objects.get(id = pid)
2321 # add conf variables
2322 if 'configvarAdd' in request.POST:
2323 t=request.POST['configvarAdd'].strip()
2324 if ":" in t:
2325 variable, value = t.split(":")
2326 else:
2327 variable = t
2328 value = ""
2329
2330 pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable, value = value)
2331 # change conf variables
2332 if 'configvarChange' in request.POST:
2333 t=request.POST['configvarChange'].strip()
2334 if ":" in t:
2335 variable, value = t.split(":")
2336 else:
2337 variable = t
2338 value = ""
2339
2340 pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable)
2341 pt.value=value
2342 pt.save()
2343 # remove conf variables
2344 if 'configvarDel' in request.POST:
2345 t=request.POST['configvarDel'].strip()
2346 pt = ProjectVariable.objects.get(pk = int(t)).delete()
2347
2348 # return all project settings, filter out blacklist and elsewhere-managed variables
2349 vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context()
2350 configvars_query = ProjectVariable.objects.filter(project_id = pid).all()
2351 for var in vars_managed:
2352 configvars_query = configvars_query.exclude(name = var)
2353 for var in vars_blacklist:
2354 configvars_query = configvars_query.exclude(name = var)
2355
2356 return_data = {
2357 "error": "ok",
2358 'configvars' : map(lambda x: (x.name, x.value, x.pk), configvars_query),
2359 }
2360 try:
2361 return_data['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value,
2362 except ProjectVariable.DoesNotExist:
2363 pass
2364 try:
2365 return_data['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value,
2366 except ProjectVariable.DoesNotExist:
2367 pass
2368 try:
2369 return_data['image_install_append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL_append").value,
2370 except ProjectVariable.DoesNotExist:
2371 pass
2372 try:
2373 return_data['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value,
2374 except ProjectVariable.DoesNotExist:
2375 pass
2376 try:
2377 return_data['sdk_machine'] = ProjectVariable.objects.get(project = prj, name = "SDKMACHINE").value,
2378 except ProjectVariable.DoesNotExist:
2379 pass
2380
2381 return HttpResponse(json.dumps( return_data ), content_type = "application/json")
2382
2383 except Exception as e:
2384 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
2385
2386
2387 def xhr_importlayer(request):
2388 if (not request.POST.has_key('vcs_url') or
2389 not request.POST.has_key('name') or
2390 not request.POST.has_key('git_ref') or
2391 not request.POST.has_key('project_id')):
2392 return HttpResponse(jsonfilter({"error": "Missing parameters; requires vcs_url, name, git_ref and project_id"}), content_type = "application/json")
2393
2394 layers_added = [];
2395
2396 # Rudimentary check for any possible html tags
2397 if "<" in request.POST:
2398 return HttpResponse(jsonfilter({"error": "Invalid character <"}), content_type = "application/json")
2399
2400 prj = Project.objects.get(pk=request.POST['project_id'])
2401
2402 # Strip trailing/leading whitespace from all values
2403 # put into a new dict because POST one is immutable
2404 post_data = dict()
2405 for key,val in request.POST.iteritems():
2406 post_data[key] = val.strip()
2407
2408
2409 # We need to know what release the current project is so that we
2410 # can set the imported layer's up_branch_id
2411 prj_branch_name = Release.objects.get(pk=prj.release_id).branch_name
2412 up_branch, branch_created = Branch.objects.get_or_create(name=prj_branch_name, layer_source_id=LayerSource.TYPE_IMPORTED)
2413
2414 layer_source = LayerSource.objects.get(sourcetype=LayerSource.TYPE_IMPORTED)
2415 try:
2416 layer, layer_created = Layer.objects.get_or_create(name=post_data['name'])
2417 except MultipleObjectsReturned:
2418 return HttpResponse(jsonfilter({"error": "hint-layer-exists"}), content_type = "application/json")
2419
2420 if layer:
2421 if layer_created:
2422 layer.layer_source = layer_source
2423 layer.vcs_url = post_data['vcs_url']
2424 layer.up_date = timezone.now()
2425 layer.save()
2426 else:
2427 # We have an existing layer by this name, let's see if the git
2428 # url is the same, if it is then we can just create a new layer
2429 # version for this layer. Otherwise we need to bail out.
2430 if layer.vcs_url != post_data['vcs_url']:
2431 return HttpResponse(jsonfilter({"error": "hint-layer-exists-with-different-url" , "current_url" : layer.vcs_url, "current_id": layer.id }), content_type = "application/json")
2432
2433
2434 layer_version, version_created = Layer_Version.objects.get_or_create(layer_source=layer_source, layer=layer, project=prj, up_branch_id=up_branch.id,branch=post_data['git_ref'], commit=post_data['git_ref'], dirpath=post_data['dir_path'])
2435
2436 if layer_version:
2437 if not version_created:
2438 return HttpResponse(jsonfilter({"error": "hint-layer-version-exists", "existing_layer_version": layer_version.id }), content_type = "application/json")
2439
2440 layer_version.up_date = timezone.now()
2441 layer_version.save()
2442
2443 # Add the dependencies specified for this new layer
2444 if (post_data.has_key("layer_deps") and
2445 version_created and
2446 len(post_data["layer_deps"]) > 0):
2447 for layer_dep_id in post_data["layer_deps"].split(","):
2448
2449 layer_dep_obj = Layer_Version.objects.get(pk=layer_dep_id)
2450 LayerVersionDependency.objects.get_or_create(layer_version=layer_version, depends_on=layer_dep_obj)
2451 # Now add them to the project, we could get an execption
2452 # if the project now contains the exact
2453 # dependency already (like modified on another page)
2454 try:
2455 prj_layer, prj_layer_created = ProjectLayer.objects.get_or_create(layercommit=layer_dep_obj, project=prj)
2456 except IntegrityError as e:
2457 logger.warning("Integrity error while saving Project Layers: %s (original %s)" % (e, e.__cause__))
2458 continue
2459
2460 if prj_layer_created:
2461 layerdepdetailurl = reverse('layerdetails', args=(prj.id, layer_dep_obj.pk))
2462 layers_added.append({'id': layer_dep_obj.id, 'name': Layer.objects.get(id=layer_dep_obj.layer_id).name, 'layerdetailurl': layerdepdetailurl })
2463
2464
2465 # If an old layer version exists in our project then remove it
2466 for prj_layers in ProjectLayer.objects.filter(project=prj):
2467 dup_layer_v = Layer_Version.objects.filter(id=prj_layers.layercommit_id, layer_id=layer.id)
2468 if len(dup_layer_v) >0 :
2469 prj_layers.delete()
2470
2471 # finally add the imported layer (version id) to the project
2472 ProjectLayer.objects.create(layercommit=layer_version, project=prj,optional=1)
2473
2474 else:
2475 # We didn't create a layer version so back out now and clean up.
2476 if layer_created:
2477 layer.delete()
2478
2479 return HttpResponse(jsonfilter({"error": "Uncaught error: Could not create layer version"}), content_type = "application/json")
2480
2481 layerdetailurl = reverse('layerdetails', args=(prj.id, layer_version.pk))
2482
2483 json_response = {"error": "ok",
2484 "imported_layer" : {
2485 "name" : layer.name,
2486 "id": layer_version.id,
2487 "layerdetailurl": layerdetailurl,
2488 },
2489 "deps_added": layers_added }
2490
2491 return HttpResponse(jsonfilter(json_response), content_type = "application/json")
2492
2493 def xhr_updatelayer(request):
2494
2495 def error_response(error):
2496 return HttpResponse(jsonfilter({"error": error}), content_type = "application/json")
2497
2498 if not request.POST.has_key("layer_version_id"):
2499 return error_response("Please specify a layer version id")
2500 try:
2501 layer_version_id = request.POST["layer_version_id"]
2502 layer_version = Layer_Version.objects.get(id=layer_version_id)
2503 except Layer_Version.DoesNotExist:
2504 return error_response("Cannot find layer to update")
2505
2506
2507 if request.POST.has_key("vcs_url"):
2508 layer_version.layer.vcs_url = request.POST["vcs_url"]
2509 if request.POST.has_key("dirpath"):
2510 layer_version.dirpath = request.POST["dirpath"]
2511 if request.POST.has_key("commit"):
2512 layer_version.commit = request.POST["commit"]
2513 if request.POST.has_key("up_branch"):
2514 layer_version.up_branch_id = int(request.POST["up_branch"])
2515
2516 if request.POST.has_key("add_dep"):
2517 lvd = LayerVersionDependency(layer_version=layer_version, depends_on_id=request.POST["add_dep"])
2518 lvd.save()
2519
2520 if request.POST.has_key("rm_dep"):
2521 rm_dep = LayerVersionDependency.objects.get(layer_version=layer_version, depends_on_id=request.POST["rm_dep"])
2522 rm_dep.delete()
2523
2524 if request.POST.has_key("summary"):
2525 layer_version.layer.summary = request.POST["summary"]
2526 if request.POST.has_key("description"):
2527 layer_version.layer.description = request.POST["description"]
2528
2529 try:
2530 layer_version.layer.save()
2531 layer_version.save()
2532 except Exception as e:
2533 return error_response("Could not update layer version entry: %s" % e)
2534
2535 return HttpResponse(jsonfilter({"error": "ok",}), content_type = "application/json")
2536
2537
2538
2539 def importlayer(request, pid):
2540 template = "importlayer.html"
2541 context = {
2542 'project': Project.objects.get(id=pid),
2543 }
2544 return render(request, template, context)
2545
2546 @_template_renderer('layerdetails.html')
2547 def layerdetails(request, pid, layerid):
2548 project = Project.objects.get(pk=pid)
2549 layer_version = Layer_Version.objects.get(pk=layerid)
2550
2551 context = { 'project' : project,
2552 'layerversion' : layer_version,
2553 'layerdeps' : { "list": [
2554 [{"id": y.id, "name": y.layer.name} for y in x.depends_on.get_equivalents_wpriority(project)][0] for x in layer_version.dependencies.all()]},
2555 'projectlayers': map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=project))
2556 }
2557
2558 return context
2559
2560
2561 def get_project_configvars_context():
2562 # Vars managed outside of this view
2563 vars_managed = {
2564 'MACHINE', 'BBLAYERS'
2565 }
2566
2567 vars_blacklist = {
2568 'DL_DR','PARALLEL_MAKE','BB_NUMBER_THREADS','SSTATE_DIR',
2569 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT',
2570 'DL_DIR','PARALLEL_MAKE','SSTATE_DIR','SSTATE_DIR','SSTATE_MIRRORS','TMPDIR',
2571 'all_proxy','ftp_proxy','http_proxy ','https_proxy'
2572 }
2573
2574 vars_fstypes = {
2575 'btrfs','cpio','cpio.gz','cpio.lz4','cpio.lzma','cpio.xz','cramfs',
2576 'elf','ext2','ext2.bz2','ext2.gz','ext2.lzma', 'ext4', 'ext4.gz', 'ext3','ext3.gz','hddimg',
2577 'iso','jffs2','jffs2.sum','squashfs','squashfs-lzo','squashfs-xz','tar.bz2',
2578 'tar.lz4','tar.xz','tartar.gz','ubi','ubifs','vmdk'
2579 }
2580
2581 return(vars_managed,sorted(vars_fstypes),vars_blacklist)
2582
2583 @_template_renderer("projectconf.html")
2584 def projectconf(request, pid):
2585
2586 try:
2587 prj = Project.objects.get(id = pid)
2588 except Project.DoesNotExist:
2589 return HttpResponseNotFound("<h1>Project id " + pid + " is unavailable</h1>")
2590
2591 # remove blacklist and externally managed varaibles from this list
2592 vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context()
2593 configvars = ProjectVariable.objects.filter(project_id = pid).all()
2594 for var in vars_managed:
2595 configvars = configvars.exclude(name = var)
2596 for var in vars_blacklist:
2597 configvars = configvars.exclude(name = var)
2598
2599 context = {
2600 'project': prj,
2601 'configvars': configvars,
2602 'vars_managed': vars_managed,
2603 'vars_fstypes': vars_fstypes,
2604 'vars_blacklist': vars_blacklist,
2605 }
2606
2607 try:
2608 context['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value
2609 context['distro_defined'] = "1"
2610 except ProjectVariable.DoesNotExist:
2611 pass
2612 try:
2613 context['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value
2614 context['fstypes_defined'] = "1"
2615 except ProjectVariable.DoesNotExist:
2616 pass
2617 try:
2618 context['image_install_append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL_append").value
2619 context['image_install_append_defined'] = "1"
2620 except ProjectVariable.DoesNotExist:
2621 pass
2622 try:
2623 context['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value
2624 context['package_classes_defined'] = "1"
2625 except ProjectVariable.DoesNotExist:
2626 pass
2627 try:
2628 context['sdk_machine'] = ProjectVariable.objects.get(project = prj, name = "SDKMACHINE").value
2629 context['sdk_machine_defined'] = "1"
2630 except ProjectVariable.DoesNotExist:
2631 pass
2632
2633 return context
2634
2635 @_template_renderer('projectbuilds.html')
2636 def projectbuilds(request, pid):
2637 prj = Project.objects.get(id = pid)
2638
2639 if request.method == "POST":
2640 # process any build request
2641
2642 if 'buildCancel' in request.POST:
2643 for i in request.POST['buildCancel'].strip().split(" "):
2644 try:
2645 br = BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_QUEUED)
2646 br.state = BuildRequest.REQ_DELETED
2647 br.save()
2648 except BuildRequest.DoesNotExist:
2649 pass
2650
2651 if 'buildDelete' in request.POST:
2652 for i in request.POST['buildDelete'].strip().split(" "):
2653 try:
2654 br = BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_DELETED).delete()
2655 except BuildRequest.DoesNotExist:
2656 pass
2657
2658 if 'targets' in request.POST:
2659 ProjectTarget.objects.filter(project = prj).delete()
2660 s = str(request.POST['targets'])
2661 for t in s.translate(None, ";%|\"").split(" "):
2662 if ":" in t:
2663 target, task = t.split(":")
2664 else:
2665 target = t
2666 task = ""
2667 ProjectTarget.objects.create(project = prj, target = target, task = task)
2668
2669 br = prj.schedule_build()
2670
2671
2672 queryset = Build.objects.filter(outcome__lte = Build.IN_PROGRESS)
2673
2674 try:
2675 context, pagesize, orderby = _build_list_helper(request, queryset)
2676 except RedirectException as re:
2677 # rewrite the RedirectException with our current url information
2678 re.view = resolve(request.path_info).url_name
2679 re.okwargs = {"pid" : pid}
2680 raise re
2681
2682 context['project'] = prj
2683 _set_parameters_values(pagesize, orderby, request)
2684
2685 return context
2686
2687
2688 def _file_name_for_artifact(b, artifact_type, artifact_id):
2689 file_name = None
2690 # Target_Image_File file_name
2691 if artifact_type == "imagefile":
2692 file_name = Target_Image_File.objects.get(target__build = b, pk = artifact_id).file_name
2693
2694 elif artifact_type == "buildartifact":
2695 file_name = BuildArtifact.objects.get(build = b, pk = artifact_id).file_name
2696
2697 elif artifact_type == "licensemanifest":
2698 file_name = Target.objects.get(build = b, pk = artifact_id).license_manifest_path
2699
2700 elif artifact_type == "tasklogfile":
2701 file_name = Task.objects.get(build = b, pk = artifact_id).logfile
2702
2703 elif artifact_type == "logmessagefile":
2704 file_name = LogMessage.objects.get(build = b, pk = artifact_id).pathname
2705 else:
2706 raise Exception("FIXME: artifact type %s not implemented" % (artifact_type))
2707
2708 return file_name
2709
2710
2711 def build_artifact(request, build_id, artifact_type, artifact_id):
2712 if artifact_type in ["cookerlog"]:
2713 # these artifacts are saved after building, so they are on the server itself
2714 def _mimetype_for_artifact(path):
2715 try:
2716 import magic
2717
2718 # fair warning: this is a mess; there are multiple competing and incompatible
2719 # magic modules floating around, so we try some of the most common combinations
2720
2721 try: # we try ubuntu's python-magic 5.4
2722 m = magic.open(magic.MAGIC_MIME_TYPE)
2723 m.load()
2724 return m.file(path)
2725 except AttributeError:
2726 pass
2727
2728 try: # we try python-magic 0.4.6
2729 m = magic.Magic(magic.MAGIC_MIME)
2730 return m.from_file(path)
2731 except AttributeError:
2732 pass
2733
2734 try: # we try pip filemagic 1.6
2735 m = magic.Magic(flags=magic.MAGIC_MIME_TYPE)
2736 return m.id_filename(path)
2737 except AttributeError:
2738 pass
2739
2740 return "binary/octet-stream"
2741 except ImportError:
2742 return "binary/octet-stream"
2743 try:
2744 # match code with runbuilds.Command.archive()
2745 build_artifact_storage_dir = os.path.join(ToasterSetting.objects.get(name="ARTIFACTS_STORAGE_DIR").value, "%d" % int(build_id))
2746 file_name = os.path.join(build_artifact_storage_dir, "cooker_log.txt")
2747
2748 fsock = open(file_name, "r")
2749 content_type=_mimetype_for_artifact(file_name)
2750
2751 response = HttpResponse(fsock, content_type = content_type)
2752
2753 response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(file_name)
2754 return response
2755 except IOError:
2756 context = {
2757 'build' : Build.objects.get(pk = build_id),
2758 }
2759 return render(request, "unavailable_artifact.html", context)
2760
2761 else:
2762 # retrieve the artifact directly from the build environment
2763 return _get_be_artifact(request, build_id, artifact_type, artifact_id)
2764
2765
2766 def _get_be_artifact(request, build_id, artifact_type, artifact_id):
2767 try:
2768 b = Build.objects.get(pk=build_id)
2769 if b.buildrequest is None or b.buildrequest.environment is None:
2770 raise Exception("Artifact not available for download (missing build request or build environment)")
2771
2772 file_name = _file_name_for_artifact(b, artifact_type, artifact_id)
2773 fsock = None
2774 content_type='application/force-download'
2775
2776 if file_name is None:
2777 raise Exception("Could not handle artifact %s id %s" % (artifact_type, artifact_id))
2778 else:
2779 content_type = b.buildrequest.environment.get_artifact_type(file_name)
2780 fsock = b.buildrequest.environment.get_artifact(file_name)
2781 file_name = os.path.basename(file_name) # we assume that the build environment system has the same path conventions as host
2782
2783 response = HttpResponse(fsock, content_type = content_type)
2784
2785 # returns a file from the environment
2786 response['Content-Disposition'] = 'attachment; filename=' + file_name
2787 return response
2788 except IOError:
2789 context = {
2790 'build' : Build.objects.get(pk = build_id),
2791 }
2792 return render(request, "unavailable_artifact.html", context)
2793
2794
2795
2796
2797 @_template_renderer("projects.html")
2798 def projects(request):
2799 (pagesize, orderby) = _get_parameters_values(request, 10, 'updated:-')
2800 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
2801 retval = _verify_parameters( request.GET, mandatory_parameters )
2802 if retval:
2803 raise RedirectException( 'all-projects', request.GET, mandatory_parameters )
2804
2805 queryset_all = Project.objects.all()
2806
2807 # annotate each project with its number of builds
2808 queryset_all = queryset_all.annotate(num_builds=Count('build'))
2809
2810 # exclude the command line builds project if it has no builds
2811 q_default_with_builds = Q(is_default=True) & Q(num_builds__gt=0)
2812 queryset_all = queryset_all.filter(Q(is_default=False) |
2813 q_default_with_builds)
2814
2815 # boilerplate code that takes a request for an object type and returns a queryset
2816 # for that object type. copypasta for all needed table searches
2817 (filter_string, search_term, ordering_string) = _search_tuple(request, Project)
2818 queryset_with_search = _get_queryset(Project, queryset_all, None, search_term, ordering_string, '-updated')
2819 queryset = _get_queryset(Project, queryset_all, filter_string, search_term, ordering_string, '-updated')
2820
2821 # retrieve the objects that will be displayed in the table; projects a paginator and gets a page range to display
2822 project_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
2823
2824 # add fields needed in JSON dumps for API call support
2825 for p in project_info.object_list:
2826 p.id = p.pk
2827 p.projectPageUrl = reverse('project', args=(p.id,))
2828 p.layersTypeAheadUrl = reverse('xhr_layerstypeahead', args=(p.id,))
2829 p.recipesTypeAheadUrl = reverse('xhr_recipestypeahead', args=(p.id,))
2830 p.projectBuildsUrl = reverse('projectbuilds', args=(p.id,))
2831
2832 # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
2833 build_mru = _get_latest_builds()
2834
2835 # translate the project's build target strings
2836 fstypes_map = {};
2837 for project in project_info:
2838 try:
2839 targets = Target.objects.filter( build_id = project.get_last_build_id() )
2840 comma = "";
2841 extensions = "";
2842 for t in targets:
2843 if ( not t.is_image ):
2844 continue
2845 tif = Target_Image_File.objects.filter( target_id = t.id )
2846 for i in tif:
2847 s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
2848 if s == i.file_name:
2849 s=re.sub('.*\.', '', i.file_name)
2850 if None == re.search(s,extensions):
2851 extensions += comma + s
2852 comma = ", "
2853 fstypes_map[project.id]=extensions
2854 except (Target.DoesNotExist,IndexError):
2855 fstypes_map[project.id]=project.get_last_imgfiles
2856
2857 context = {
2858 'mru' : build_mru,
2859
2860 'objects' : project_info,
2861 'objectname' : "projects",
2862 'default_orderby' : 'id:-',
2863 'search_term' : search_term,
2864 'total_count' : queryset_with_search.count(),
2865 'fstypes' : fstypes_map,
2866 'build_FAILED' : Build.FAILED,
2867 'build_SUCCEEDED' : Build.SUCCEEDED,
2868 'tablecols': [
2869 {'name': 'Project',
2870 'orderfield': _get_toggle_order(request, "name"),
2871 'ordericon':_get_toggle_order_icon(request, "name"),
2872 'orderkey' : 'name',
2873 },
2874 {'name': 'Last activity on',
2875 'clclass': 'updated',
2876 '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",
2877 'orderfield': _get_toggle_order(request, "updated", True),
2878 'ordericon':_get_toggle_order_icon(request, "updated"),
2879 'orderkey' : 'updated',
2880 },
2881 {'name': 'Release',
2882 'qhelp' : "The version of the build system used by the project",
2883 'orderfield': _get_toggle_order(request, "release__name"),
2884 'ordericon':_get_toggle_order_icon(request, "release__name"),
2885 'orderkey' : 'release__name',
2886 },
2887 {'name': 'Machine',
2888 'qhelp': "The hardware currently selected for the project",
2889 },
2890 {'name': 'Number of builds',
2891 'qhelp': "How many builds have been run for the project",
2892 },
2893 {'name': 'Last build outcome', 'clclass': 'loutcome',
2894 'qhelp': "Tells you if the last project build completed successfully or failed",
2895 },
2896 {'name': 'Recipe', 'clclass': 'ltarget',
2897 'qhelp': "The last recipe that was built in this project",
2898 },
2899 {'name': 'Errors', 'clclass': 'lerrors',
2900 'qhelp': "How many errors were encountered during the last project build (if any)",
2901 },
2902 {'name': 'Warnings', 'clclass': 'lwarnings',
2903 'qhelp': "How many warnigns were encountered during the last project build (if any)",
2904 },
2905 {'name': 'Image files', 'clclass': 'limagefiles', 'hidden': 1,
2906 'qhelp': "The root file system types produced by the last project build",
2907 },
2908 ]
2909 }
2910
2911 _set_parameters_values(pagesize, orderby, request)
2912 return context