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