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