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