blob: d7acaff8924288f41ed379df0784e98fd6f9a536 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002# BitBake Toaster Implementation
3#
4# Copyright (C) 2013 Intel Corporation
5#
Brad Bishopc342db32019-05-15 21:57:59 -04006# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05008
Patrick Williamsc0f7c042017-02-23 20:41:17 -06009import re
Patrick Williamsc124f4f2015-09-15 14:41:29 -050010
Patrick Williamsc0f7c042017-02-23 20:41:17 -060011from django.db.models import F, Q, Sum
12from django.db import IntegrityError
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050013from django.shortcuts import render, redirect, get_object_or_404
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080014from django.utils.http import urlencode
Patrick Williamsc0f7c042017-02-23 20:41:17 -060015from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe
16from orm.models import LogMessage, Variable, Package_Dependency, Package
17from orm.models import Task_Dependency, Package_File
18from orm.models import Target_Installed_Package, Target_File
19from orm.models import TargetKernelFile, TargetSDKFile, Target_Image_File
Patrick Williamsf1e5d692016-03-30 15:21:19 -050020from orm.models import BitbakeVersion, CustomImageRecipe
Patrick Williamsc0f7c042017-02-23 20:41:17 -060021
Patrick Williamsc124f4f2015-09-15 14:41:29 -050022from django.core.urlresolvers import reverse, resolve
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050023from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
Brad Bishopd7bf8c12018-02-25 22:55:05 -050025from django.http import HttpResponseNotFound, JsonResponse
Patrick Williamsc124f4f2015-09-15 14:41:29 -050026from django.utils import timezone
Patrick Williamsd7e96312015-09-22 08:09:05 -050027from datetime import timedelta, datetime
Patrick Williamsc124f4f2015-09-15 14:41:29 -050028from toastergui.templatetags.projecttags import json as jsonfilter
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050029from decimal import Decimal
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030import json
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050031import os
Patrick Williamsc124f4f2015-09-15 14:41:29 -050032from os.path import dirname
Patrick Williamsf1e5d692016-03-30 15:21:19 -050033import mimetypes
Patrick Williamsc124f4f2015-09-15 14:41:29 -050034
35import logging
36
37logger = logging.getLogger("toaster")
38
Brad Bishopd7bf8c12018-02-25 22:55:05 -050039# Project creation and managed build enable
40project_enable = ('1' == os.environ.get('TOASTER_BUILDSERVER'))
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080041is_project_specific = ('1' == os.environ.get('TOASTER_PROJECTSPECIFIC'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -060042
Patrick Williamsd7e96312015-09-22 08:09:05 -050043class MimeTypeFinder(object):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050044 # setting this to False enables additional non-standard mimetypes
45 # to be included in the guess
46 _strict = False
Patrick Williamsd7e96312015-09-22 08:09:05 -050047
Patrick Williamsf1e5d692016-03-30 15:21:19 -050048 # returns the mimetype for a file path as a string,
49 # or 'application/octet-stream' if the type couldn't be guessed
Patrick Williamsd7e96312015-09-22 08:09:05 -050050 @classmethod
51 def get_mimetype(self, path):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050052 guess = mimetypes.guess_type(path, self._strict)
53 guessed_type = guess[0]
54 if guessed_type == None:
55 guessed_type = 'application/octet-stream'
56 return guessed_type
Patrick Williamsd7e96312015-09-22 08:09:05 -050057
Brad Bishopd7bf8c12018-02-25 22:55:05 -050058# single point to add global values into the context before rendering
59def toaster_render(request, page, context):
60 context['project_enable'] = project_enable
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080061 context['project_specific'] = is_project_specific
Brad Bishopd7bf8c12018-02-25 22:55:05 -050062 return render(request, page, context)
63
64
Patrick Williamsc124f4f2015-09-15 14:41:29 -050065# all new sessions should come through the landing page;
66# determine in which mode we are running in, and redirect appropriately
67def landing(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050068 # in build mode, we redirect to the command-line builds page
69 # if there are any builds for the default (cli builds) project
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050070 default_project = Project.objects.get_or_create_default_project()
Patrick Williamsf1e5d692016-03-30 15:21:19 -050071 default_project_builds = Build.objects.filter(project = default_project)
72
Patrick Williamsc124f4f2015-09-15 14:41:29 -050073 # we only redirect to projects page if there is a user-generated project
Patrick Williamsf1e5d692016-03-30 15:21:19 -050074 num_builds = Build.objects.all().count()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050075 user_projects = Project.objects.filter(is_default = False)
76 has_user_project = user_projects.count() > 0
77
Patrick Williamsf1e5d692016-03-30 15:21:19 -050078 if num_builds == 0 and has_user_project:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050079 return redirect(reverse('all-projects'), permanent = False)
80
Patrick Williamsf1e5d692016-03-30 15:21:19 -050081 if num_builds > 0:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050082 return redirect(reverse('all-builds'), permanent = False)
83
84 context = {'lvs_nos' : Layer_Version.objects.all().count()}
85
Brad Bishopd7bf8c12018-02-25 22:55:05 -050086 return toaster_render(request, 'landing.html', context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050087
Patrick Williamsc124f4f2015-09-15 14:41:29 -050088def objtojson(obj):
89 from django.db.models.query import QuerySet
90 from django.db.models import Model
91
92 if isinstance(obj, datetime):
93 return obj.isoformat()
94 elif isinstance(obj, timedelta):
95 return obj.total_seconds()
96 elif isinstance(obj, QuerySet) or isinstance(obj, set):
97 return list(obj)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050098 elif isinstance(obj, Decimal):
99 return str(obj)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500100 elif type(obj).__name__ == "RelatedManager":
101 return [x.pk for x in obj.all()]
102 elif hasattr( obj, '__dict__') and isinstance(obj, Model):
103 d = obj.__dict__
104 nd = dict(d)
105 for di in d.keys():
106 if di.startswith("_"):
107 del nd[di]
108 elif isinstance(d[di], Model):
109 nd[di] = d[di].pk
110 elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di):
111 nd[di] = getattr(obj, "get_%s_display" % di)()
112 return nd
113 elif isinstance( obj, type(lambda x:x)):
114 import inspect
115 return inspect.getsourcelines(obj)[0]
116 else:
117 raise TypeError("Unserializable object %s (%s) of type %s" % ( obj, dir(obj), type(obj)))
118
119
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500120def _lv_to_dict(prj, x = None):
121 if x is None:
122 def wrapper(x):
123 return _lv_to_dict(prj, x)
124 return wrapper
125
126 return {"id": x.pk,
127 "name": x.layer.name,
128 "tooltip": "%s | %s" % (x.layer.vcs_url,x.get_vcs_reference()),
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600129 "detail": "(%s" % x.layer.vcs_url + (")" if x.release == None else " | "+x.get_vcs_reference()+")"),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500130 "giturl": x.layer.vcs_url,
131 "layerdetailurl" : reverse('layerdetails', args=(prj.id,x.pk)),
132 "revision" : x.get_vcs_reference(),
133 }
134
135
136def _build_page_range(paginator, index = 1):
137 try:
138 page = paginator.page(index)
139 except PageNotAnInteger:
140 page = paginator.page(1)
141 except EmptyPage:
142 page = paginator.page(paginator.num_pages)
143
144
145 page.page_range = [page.number]
146 crt_range = 0
147 for i in range(1,5):
148 if (page.number + i) <= paginator.num_pages:
149 page.page_range = page.page_range + [ page.number + i]
150 crt_range +=1
151 if (page.number - i) > 0:
152 page.page_range = [page.number -i] + page.page_range
153 crt_range +=1
154 if crt_range == 4:
155 break
156 return page
157
158
159def _verify_parameters(g, mandatory_parameters):
160 miss = []
161 for mp in mandatory_parameters:
162 if not mp in g:
163 miss.append(mp)
164 if len(miss):
165 return miss
166 return None
167
168def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600169 try:
170 from urllib import unquote, urlencode
171 except ImportError:
172 from urllib.parse import unquote, urlencode
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500173 url = reverse(view, kwargs=kwargs)
174 params = {}
175 for i in g:
176 params[i] = g[i]
177 for i in mandatory_parameters:
178 if not i in params:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600179 params[i] = unquote(str(mandatory_parameters[i]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500180
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600181 return redirect(url + "?%s" % urlencode(params), permanent = False, **kwargs)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500182
183class RedirectException(Exception):
184 def __init__(self, view, g, mandatory_parameters, *args, **kwargs):
185 super(RedirectException, self).__init__()
186 self.view = view
187 self.g = g
188 self.mandatory_parameters = mandatory_parameters
189 self.oargs = args
190 self.okwargs = kwargs
191
192 def get_redirect_response(self):
193 return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, **self.okwargs)
194
195FIELD_SEPARATOR = ":"
196AND_VALUE_SEPARATOR = "!"
197OR_VALUE_SEPARATOR = "|"
198DESCENDING = "-"
199
200def __get_q_for_val(name, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600201 if "OR" in value or "AND" in value:
202 result = None
203 for x in value.split("OR"):
204 x = __get_q_for_val(name, x)
205 result = result | x if result else x
206 return result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500207 if "AND" in value:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600208 result = None
209 for x in value.split("AND"):
210 x = __get_q_for_val(name, x)
211 result = result & x if result else x
212 return result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500213 if value.startswith("NOT"):
214 value = value[3:]
215 if value == 'None':
216 value = None
217 kwargs = { name : value }
218 return ~Q(**kwargs)
219 else:
220 if value == 'None':
221 value = None
222 kwargs = { name : value }
223 return Q(**kwargs)
224
225def _get_filtering_query(filter_string):
226
227 search_terms = filter_string.split(FIELD_SEPARATOR)
228 and_keys = search_terms[0].split(AND_VALUE_SEPARATOR)
229 and_values = search_terms[1].split(AND_VALUE_SEPARATOR)
230
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600231 and_query = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500232 for kv in zip(and_keys, and_values):
233 or_keys = kv[0].split(OR_VALUE_SEPARATOR)
234 or_values = kv[1].split(OR_VALUE_SEPARATOR)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600235 query = None
236 for key, val in zip(or_keys, or_values):
237 x = __get_q_for_val(key, val)
238 query = query | x if query else x
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500239
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600240 and_query = and_query & query if and_query else query
241
242 return and_query
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500243
244def _get_toggle_order(request, orderkey, toggle_reverse = False):
245 if toggle_reverse:
246 return "%s:+" % orderkey if request.GET.get('orderby', "") == "%s:-" % orderkey else "%s:-" % orderkey
247 else:
248 return "%s:-" % orderkey if request.GET.get('orderby', "") == "%s:+" % orderkey else "%s:+" % orderkey
249
250def _get_toggle_order_icon(request, orderkey):
251 if request.GET.get('orderby', "") == "%s:+"%orderkey:
252 return "down"
253 elif request.GET.get('orderby', "") == "%s:-"%orderkey:
254 return "up"
255 else:
256 return None
257
258# we check that the input comes in a valid form that we can recognize
259def _validate_input(field_input, model):
260
261 invalid = None
262
263 if field_input:
264 field_input_list = field_input.split(FIELD_SEPARATOR)
265
266 # Check we have only one colon
267 if len(field_input_list) != 2:
268 invalid = "We have an invalid number of separators: " + field_input + " -> " + str(field_input_list)
269 return None, invalid
270
271 # Check we have an equal number of terms both sides of the colon
272 if len(field_input_list[0].split(AND_VALUE_SEPARATOR)) != len(field_input_list[1].split(AND_VALUE_SEPARATOR)):
273 invalid = "Not all arg names got values"
274 return None, invalid + str(field_input_list)
275
276 # Check we are looking for a valid field
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500277 valid_fields = [f.name for f in model._meta.get_fields()]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500278 for field in field_input_list[0].split(AND_VALUE_SEPARATOR):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600279 if True in [field.startswith(x) for x in valid_fields]:
280 break
281 else:
282 return None, (field, valid_fields)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500283
284 return field_input, invalid
285
286# uses search_allowed_fields in orm/models.py to create a search query
287# for these fields with the supplied input text
288def _get_search_results(search_term, queryset, model):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600289 search_object = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500290 for st in search_term.split(" "):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600291 queries = None
292 for field in model.search_allowed_fields:
293 query = Q(**{field + '__icontains': st})
294 queries = queries | query if queries else query
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500295
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600296 search_object = search_object & queries if search_object else queries
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500297 queryset = queryset.filter(search_object)
298
299 return queryset
300
301
302# function to extract the search/filter/ordering parameters from the request
303# it uses the request and the model to validate input for the filter and orderby values
304def _search_tuple(request, model):
305 ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model)
306 if invalid:
307 raise BaseException("Invalid ordering model:" + str(model) + str(invalid))
308
309 filter_string, invalid = _validate_input(request.GET.get('filter', ''), model)
310 if invalid:
311 raise BaseException("Invalid filter " + str(invalid))
312
313 search_term = request.GET.get('search', '')
314 return (filter_string, search_term, ordering_string)
315
316
317# returns a lazy-evaluated queryset for a filter/search/order combination
318def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''):
319 if filter_string:
320 filter_query = _get_filtering_query(filter_string)
321 queryset = queryset.filter(filter_query)
322 else:
323 queryset = queryset.all()
324
325 if search_term:
326 queryset = _get_search_results(search_term, queryset, model)
327
328 if ordering_string:
329 column, order = ordering_string.split(':')
330 if column == re.sub('-','',ordering_secondary):
331 ordering_secondary=''
332 if order.lower() == DESCENDING:
333 column = '-' + column
334 if ordering_secondary:
335 queryset = queryset.order_by(column, ordering_secondary)
336 else:
337 queryset = queryset.order_by(column)
338
339 # insure only distinct records (e.g. from multiple search hits) are returned
340 return queryset.distinct()
341
342# returns the value of entries per page and the name of the applied sorting field.
343# if the value is given explicitly as a GET parameter it will be the first selected,
344# otherwise the cookie value will be used.
345def _get_parameters_values(request, default_count, default_order):
346 current_url = resolve(request.path_info).url_name
347 pagesize = request.GET.get('count', request.session.get('%s_count' % current_url, default_count))
348 orderby = request.GET.get('orderby', request.session.get('%s_orderby' % current_url, default_order))
349 return (pagesize, orderby)
350
351
352# set cookies for parameters. this is usefull in case parameters are set
353# manually from the GET values of the link
354def _set_parameters_values(pagesize, orderby, request):
355 from django.core.urlresolvers import resolve
356 current_url = resolve(request.path_info).url_name
357 request.session['%s_count' % current_url] = pagesize
358 request.session['%s_orderby' % current_url] =orderby
359
360# date range: normalize GUI's dd/mm/yyyy to date object
361def _normalize_input_date(date_str,default):
362 date_str=re.sub('/', '-', date_str)
363 # accept dd/mm/yyyy to d/m/yy
364 try:
365 date_in = datetime.strptime(date_str, "%d-%m-%Y")
366 except ValueError:
367 # courtesy try with two digit year
368 try:
369 date_in = datetime.strptime(date_str, "%d-%m-%y")
370 except ValueError:
371 return default
372 date_in = date_in.replace(tzinfo=default.tzinfo)
373 return date_in
374
375# convert and normalize any received date range filter, for example:
376# "completed_on__gte!completed_on__lt:01/03/2015!02/03/2015_daterange" to
377# "completed_on__gte!completed_on__lt:2015-03-01!2015-03-02"
378def _modify_date_range_filter(filter_string):
379 # was the date range radio button selected?
380 if 0 > filter_string.find('_daterange'):
381 return filter_string,''
382 # normalize GUI dates to database format
383 filter_string = filter_string.replace('_daterange','').replace(':','!');
384 filter_list = filter_string.split('!');
385 if 4 != len(filter_list):
386 return filter_string
387 today = timezone.localtime(timezone.now())
388 date_id = filter_list[1]
389 date_from = _normalize_input_date(filter_list[2],today)
390 date_to = _normalize_input_date(filter_list[3],today)
391 # swap dates if manually set dates are out of order
392 if date_to < date_from:
393 date_to,date_from = date_from,date_to
394 # convert to strings, make 'date_to' inclusive by moving to begining of next day
395 date_from_str = date_from.strftime("%Y-%m-%d")
396 date_to_str = (date_to+timedelta(days=1)).strftime("%Y-%m-%d")
397 filter_string=filter_list[0]+'!'+filter_list[1]+':'+date_from_str+'!'+date_to_str
398 daterange_selected = re.sub('__.*','', date_id)
399 return filter_string,daterange_selected
400
401def _add_daterange_context(queryset_all, request, daterange_list):
402 # calculate the exact begining of local today and yesterday
403 today_begin = timezone.localtime(timezone.now())
Patrick Williamsd7e96312015-09-22 08:09:05 -0500404 yesterday_begin = today_begin - timedelta(days=1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500405 # add daterange persistent
406 context_date = {}
407 context_date['last_date_from'] = request.GET.get('last_date_from',timezone.localtime(timezone.now()).strftime("%d/%m/%Y"))
408 context_date['last_date_to' ] = request.GET.get('last_date_to' ,context_date['last_date_from'])
409 # calculate the date ranges, avoid second sort for 'created'
410 # fetch the respective max range from the database
411 context_date['daterange_filter']=''
412 for key in daterange_list:
413 queryset_key = queryset_all.order_by(key)
414 try:
415 context_date['dateMin_'+key]=timezone.localtime(getattr(queryset_key.first(),key)).strftime("%d/%m/%Y")
416 except AttributeError:
417 context_date['dateMin_'+key]=timezone.localtime(timezone.now())
418 try:
419 context_date['dateMax_'+key]=timezone.localtime(getattr(queryset_key.last(),key)).strftime("%d/%m/%Y")
420 except AttributeError:
421 context_date['dateMax_'+key]=timezone.localtime(timezone.now())
422 return context_date,today_begin,yesterday_begin
423
424
425##
426# build dashboard for a single build, coming in as argument
427# Each build may contain multiple targets and each target
428# may generate multiple image files. display them all.
429#
430def builddashboard( request, build_id ):
431 template = "builddashboard.html"
432 if Build.objects.filter( pk=build_id ).count( ) == 0 :
433 return redirect( builds )
434 build = Build.objects.get( pk = build_id );
435 layerVersionId = Layer_Version.objects.filter( build = build_id );
436 recipeCount = Recipe.objects.filter( layer_version__id__in = layerVersionId ).count( );
437 tgts = Target.objects.filter( build_id = build_id ).order_by( 'target' );
438
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500439 # set up custom target list with computed package and image data
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600440 targets = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500441 ntargets = 0
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600442
443 # True if at least one target for this build has an SDK artifact
444 # or image file
445 has_artifacts = False
446
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500447 for t in tgts:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600448 elem = {}
449 elem['target'] = t
450
451 target_has_images = False
452 image_files = []
453
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500454 npkg = 0
455 pkgsz = 0
456 package = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500457 # Chunk the query to avoid "too many SQL variables" error
458 package_set = t.target_installed_package_set.all()
459 package_set_len = len(package_set)
460 for ps_start in range(0,package_set_len,500):
461 ps_stop = min(ps_start+500,package_set_len)
462 for package in Package.objects.filter(id__in = [x.package_id for x in package_set[ps_start:ps_stop]]):
463 pkgsz = pkgsz + package.size
464 if package.installed_name:
465 npkg = npkg + 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600466 elem['npkg'] = npkg
467 elem['pkgsz'] = pkgsz
468 ti = Target_Image_File.objects.filter(target_id = t.id)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500469 for i in ti:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600470 ndx = i.file_name.rfind('/')
471 if ndx < 0:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500472 ndx = 0;
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600473 f = i.file_name[ndx + 1:]
474 image_files.append({
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500475 'id': i.id,
476 'path': f,
477 'size': i.file_size,
478 'suffix': i.suffix
479 })
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600480 if len(image_files) > 0:
481 target_has_images = True
482 elem['targetHasImages'] = target_has_images
483
484 elem['imageFiles'] = image_files
485 elem['target_kernel_artifacts'] = t.targetkernelfile_set.all()
486
487 target_sdk_files = t.targetsdkfile_set.all()
488 target_sdk_artifacts_count = target_sdk_files.count()
489 elem['target_sdk_artifacts_count'] = target_sdk_artifacts_count
490 elem['target_sdk_artifacts'] = target_sdk_files
491
492 if target_has_images or target_sdk_artifacts_count > 0:
493 has_artifacts = True
494
495 targets.append(elem)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500496
497 ##
498 # how many packages in this build - ignore anonymous ones
499 #
500
501 packageCount = 0
502 packages = Package.objects.filter( build_id = build_id )
503 for p in packages:
504 if ( p.installed_name ):
505 packageCount = packageCount + 1
506
507 logmessages = list(LogMessage.objects.filter( build = build_id ))
508
509 context = {
510 'build' : build,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500511 'project' : build.project,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600512 'hasArtifacts' : has_artifacts,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500513 'ntargets' : ntargets,
514 'targets' : targets,
515 'recipecount' : recipeCount,
516 'packagecount' : packageCount,
517 'logmessages' : logmessages,
518 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500519 return toaster_render( request, template, context )
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500520
521
522
523def generateCoveredList2( revlist = None ):
524 if not revlist:
525 revlist = []
526 covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
527 while len(covered_list):
528 revlist = [ x for x in revlist if x.outcome != Task.OUTCOME_COVERED ]
529 if len(revlist) > 0:
530 return revlist
531
532 newlist = _find_task_revdep_list(covered_list)
533
534 revlist = list(set(revlist + newlist))
535 covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
536 return revlist
537
538def task( request, build_id, task_id ):
539 template = "task.html"
540 tasks_list = Task.objects.filter( pk=task_id )
541 if tasks_list.count( ) == 0:
542 return redirect( builds )
543 task_object = tasks_list[ 0 ];
544 dependencies = sorted(
545 _find_task_dep( task_object ),
546 key=lambda t:'%s_%s %s'%(t.recipe.name, t.recipe.version, t.task_name))
547 reverse_dependencies = sorted(
548 _find_task_revdep( task_object ),
549 key=lambda t:'%s_%s %s'%( t.recipe.name, t.recipe.version, t.task_name ))
550 coveredBy = '';
551 if ( task_object.outcome == Task.OUTCOME_COVERED ):
552# _list = generateCoveredList( task )
553 coveredBy = sorted(generateCoveredList2( _find_task_revdep( task_object ) ), key = lambda x: x.recipe.name)
554 log_head = ''
555 log_body = ''
556 if task_object.outcome == task_object.OUTCOME_FAILED:
557 pass
558
559 uri_list= [ ]
560 variables = Variable.objects.filter(build=build_id)
561 v=variables.filter(variable_name='SSTATE_DIR')
562 if v.count() > 0:
563 uri_list.append(v[0].variable_value)
564 v=variables.filter(variable_name='SSTATE_MIRRORS')
565 if (v.count() > 0):
566 for mirror in v[0].variable_value.split('\\n'):
567 s=re.sub('.* ','',mirror.strip(' \t\n\r'))
568 if len(s):
569 uri_list.append(s)
570
571 context = {
572 'build' : Build.objects.filter( pk = build_id )[ 0 ],
573 'object' : task_object,
574 'task' : task_object,
575 'covered_by' : coveredBy,
576 'deps' : dependencies,
577 'rdeps' : reverse_dependencies,
578 'log_head' : log_head,
579 'log_body' : log_body,
580 'showing_matches' : False,
581 'uri_list' : uri_list,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600582 'task_in_tasks_table_pg': int(task_object.order / 25) + 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500583 }
584 if request.GET.get( 'show_matches', "" ):
585 context[ 'showing_matches' ] = True
586 context[ 'matching_tasks' ] = Task.objects.filter(
587 sstate_checksum=task_object.sstate_checksum ).filter(
588 build__completed_on__lt=task_object.build.completed_on).exclude(
589 order__isnull=True).exclude(outcome=Task.OUTCOME_NA).order_by('-build__completed_on')
590
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500591 return toaster_render( request, template, context )
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500592
593def recipe(request, build_id, recipe_id, active_tab="1"):
594 template = "recipe.html"
595 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
596 return redirect(builds)
597
598 recipe_object = Recipe.objects.get(pk=recipe_id)
599 layer_version = Layer_Version.objects.get(pk=recipe_object.layer_version_id)
600 layer = Layer.objects.get(pk=layer_version.layer_id)
601 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)
602 package_count = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0).count()
603
604 if active_tab != '1' and active_tab != '3' and active_tab != '4' :
605 active_tab = '1'
606 tab_states = {'1': '', '3': '', '4': ''}
607 tab_states[active_tab] = 'active'
608
609 context = {
610 'build' : Build.objects.get(pk=build_id),
611 'object' : recipe_object,
612 'layer_version' : layer_version,
613 'layer' : layer,
614 'tasks' : tasks_list,
615 'package_count' : package_count,
616 'tab_states' : tab_states,
617 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500618 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500619
620def recipe_packages(request, build_id, recipe_id):
621 template = "recipe_packages.html"
622 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
623 return redirect(builds)
624
625 (pagesize, orderby) = _get_parameters_values(request, 10, 'name:+')
626 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
627 retval = _verify_parameters( request.GET, mandatory_parameters )
628 if retval:
629 return _redirect_parameters( 'recipe_packages', request.GET, mandatory_parameters, build_id = build_id, recipe_id = recipe_id)
630 (filter_string, search_term, ordering_string) = _search_tuple(request, Package)
631
632 recipe_object = Recipe.objects.get(pk=recipe_id)
633 queryset = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0)
634 package_count = queryset.count()
635 queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name')
636
637 packages = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
638
639 context = {
640 'build' : Build.objects.get(pk=build_id),
641 'recipe' : recipe_object,
642 'objects' : packages,
643 'object_count' : package_count,
644 'tablecols':[
645 {
646 'name':'Package',
647 'orderfield': _get_toggle_order(request,"name"),
648 'ordericon': _get_toggle_order_icon(request,"name"),
649 'orderkey': "name",
650 },
651 {
652 'name':'Version',
653 },
654 {
655 'name':'Size',
656 'orderfield': _get_toggle_order(request,"size", True),
657 'ordericon': _get_toggle_order_icon(request,"size"),
658 'orderkey': 'size',
659 'dclass': 'sizecol span2',
660 },
661 ]
662 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500663 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500664 _set_parameters_values(pagesize, orderby, request)
665 return response
666
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500667from django.core.serializers.json import DjangoJSONEncoder
668from django.http import HttpResponse
669def xhr_dirinfo(request, build_id, target_id):
670 top = request.GET.get('start', '/')
671 return HttpResponse(_get_dir_entries(build_id, target_id, top), content_type = "application/json")
672
673from django.utils.functional import Promise
674from django.utils.encoding import force_text
675class LazyEncoder(json.JSONEncoder):
676 def default(self, obj):
677 if isinstance(obj, Promise):
678 return force_text(obj)
679 return super(LazyEncoder, self).default(obj)
680
681from toastergui.templatetags.projecttags import filtered_filesizeformat
682import os
683def _get_dir_entries(build_id, target_id, start):
684 node_str = {
685 Target_File.ITYPE_REGULAR : '-',
686 Target_File.ITYPE_DIRECTORY : 'd',
687 Target_File.ITYPE_SYMLINK : 'l',
688 Target_File.ITYPE_SOCKET : 's',
689 Target_File.ITYPE_FIFO : 'p',
690 Target_File.ITYPE_CHARACTER : 'c',
691 Target_File.ITYPE_BLOCK : 'b',
692 }
693 response = []
694 objects = Target_File.objects.filter(target__exact=target_id, directory__path=start)
695 target_packages = Target_Installed_Package.objects.filter(target__exact=target_id).values_list('package_id', flat=True)
696 for o in objects:
697 # exclude root inode '/'
698 if o.path == '/':
699 continue
700 try:
701 entry = {}
702 entry['parent'] = start
703 entry['name'] = os.path.basename(o.path)
704 entry['fullpath'] = o.path
705
706 # set defaults, not all dentries have packages
707 entry['installed_package'] = None
708 entry['package_id'] = None
709 entry['package'] = None
710 entry['link_to'] = None
711 if o.inodetype == Target_File.ITYPE_DIRECTORY:
712 entry['isdir'] = 1
713 # is there content in directory
714 entry['childcount'] = Target_File.objects.filter(target__exact=target_id, directory__path=o.path).all().count()
715 else:
716 entry['isdir'] = 0
717
718 # resolve the file to get the package from the resolved file
719 resolved_id = o.sym_target_id
720 resolved_path = o.path
721 if target_packages.count():
722 while resolved_id != "" and resolved_id != None:
723 tf = Target_File.objects.get(pk=resolved_id)
724 resolved_path = tf.path
725 resolved_id = tf.sym_target_id
726
727 thisfile=Package_File.objects.all().filter(path__exact=resolved_path, package_id__in=target_packages)
728 if thisfile.count():
729 p = Package.objects.get(pk=thisfile[0].package_id)
730 entry['installed_package'] = p.installed_name
731 entry['package_id'] = str(p.id)
732 entry['package'] = p.name
733 # don't use resolved path from above, show immediate link-to
734 if o.sym_target_id != "" and o.sym_target_id != None:
735 entry['link_to'] = Target_File.objects.get(pk=o.sym_target_id).path
736 entry['size'] = filtered_filesizeformat(o.size)
737 if entry['link_to'] != None:
738 entry['permission'] = node_str[o.inodetype] + o.permission
739 else:
740 entry['permission'] = node_str[o.inodetype] + o.permission
741 entry['owner'] = o.owner
742 entry['group'] = o.group
743 response.append(entry)
744
745 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600746 print("Exception ", e)
747 traceback.print_exc()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500748
749 # sort by directories first, then by name
750 rsorted = sorted(response, key=lambda entry : entry['name'])
751 rsorted = sorted(rsorted, key=lambda entry : entry['isdir'], reverse=True)
752 return json.dumps(rsorted, cls=LazyEncoder).replace('</', '<\\/')
753
754def dirinfo(request, build_id, target_id, file_path=None):
755 template = "dirinfo.html"
756 objects = _get_dir_entries(build_id, target_id, '/')
757 packages_sum = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')).aggregate(Sum('installed_size'))
758 dir_list = None
759 if file_path != None:
760 """
761 Link from the included package detail file list page and is
762 requesting opening the dir info to a specific file path.
763 Provide the list of directories to expand and the full path to
764 highlight in the page.
765 """
766 # Aassume target's path separator matches host's, that is, os.sep
767 sep = os.sep
768 dir_list = []
769 head = file_path
770 while head != sep:
771 (head, tail) = os.path.split(head)
772 if head != sep:
773 dir_list.insert(0, head)
774
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500775 build = Build.objects.get(pk=build_id)
776
777 context = { 'build': build,
778 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500779 'target': Target.objects.get(pk=target_id),
780 'packages_sum': packages_sum['installed_size__sum'],
781 'objects': objects,
782 'dir_list': dir_list,
783 'file_path': file_path,
784 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500785 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500786
787def _find_task_dep(task_object):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600788 tdeps = Task_Dependency.objects.filter(task=task_object).filter(depends_on__order__gt=0)
789 tdeps = tdeps.exclude(depends_on__outcome=Task.OUTCOME_NA).select_related("depends_on")
790 return [x.depends_on for x in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500791
792def _find_task_revdep(task_object):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600793 tdeps = Task_Dependency.objects.filter(depends_on=task_object).filter(task__order__gt=0)
794 tdeps = tdeps.exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
795
796 # exclude self-dependencies to prevent infinite dependency loop
797 # in generateCoveredList2()
798 tdeps = tdeps.exclude(task=task_object)
799
800 return [tdep.task for tdep in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500801
802def _find_task_revdep_list(tasklist):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600803 tdeps = Task_Dependency.objects.filter(depends_on__in=tasklist).filter(task__order__gt=0)
804 tdeps = tdeps.exclude(task__outcome=Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
805
806 # exclude self-dependencies to prevent infinite dependency loop
807 # in generateCoveredList2()
808 tdeps = tdeps.exclude(task=F('depends_on'))
809
810 return [tdep.task for tdep in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500811
812def _find_task_provider(task_object):
813 task_revdeps = _find_task_revdep(task_object)
814 for tr in task_revdeps:
815 if tr.outcome != Task.OUTCOME_COVERED:
816 return tr
817 for tr in task_revdeps:
818 trc = _find_task_provider(tr)
819 if trc is not None:
820 return trc
821 return None
822
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500823def configuration(request, build_id):
824 template = 'configuration.html'
825
826 var_names = ('BB_VERSION', 'BUILD_SYS', 'NATIVELSBSTRING', 'TARGET_SYS',
827 'MACHINE', 'DISTRO', 'DISTRO_VERSION', 'TUNE_FEATURES', 'TARGET_FPU')
828 context = dict(Variable.objects.filter(build=build_id, variable_name__in=var_names)\
829 .values_list('variable_name', 'variable_value'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500830 build = Build.objects.get(pk=build_id)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500831 context.update({'objectname': 'configuration',
832 'object_search_display':'variables',
833 'filter_search_display':'variables',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500834 'build': build,
835 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500836 'targets': Target.objects.filter(build=build_id)})
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500837 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500838
839
840def configvars(request, build_id):
841 template = 'configvars.html'
842 (pagesize, orderby) = _get_parameters_values(request, 100, 'variable_name:+')
843 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby, 'filter' : 'description__regex:.+' }
844 retval = _verify_parameters( request.GET, mandatory_parameters )
845 (filter_string, search_term, ordering_string) = _search_tuple(request, Variable)
846 if retval:
847 # if new search, clear the default filter
848 if search_term and len(search_term):
849 mandatory_parameters['filter']=''
850 return _redirect_parameters( 'configvars', request.GET, mandatory_parameters, build_id = build_id)
851
852 queryset = Variable.objects.filter(build=build_id).exclude(variable_name__istartswith='B_').exclude(variable_name__istartswith='do_')
853 queryset_with_search = _get_queryset(Variable, queryset, None, search_term, ordering_string, 'variable_name').exclude(variable_value='',vhistory__file_name__isnull=True)
854 queryset = _get_queryset(Variable, queryset, filter_string, search_term, ordering_string, 'variable_name')
855 # remove records where the value is empty AND there are no history files
856 queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True)
857
858 variables = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
859
860 # show all matching files (not just the last one)
861 file_filter= search_term + ":"
862 if filter_string.find('/conf/') > 0:
863 file_filter += 'conf/(local|bblayers).conf'
864 if filter_string.find('conf/machine/') > 0:
865 file_filter += 'conf/machine/'
866 if filter_string.find('conf/distro/') > 0:
867 file_filter += 'conf/distro/'
868 if filter_string.find('/bitbake.conf') > 0:
869 file_filter += '/bitbake.conf'
870 build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path)
871
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500872 build = Build.objects.get(pk=build_id)
873
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500874 context = {
875 'objectname': 'configvars',
876 'object_search_display':'BitBake variables',
877 'filter_search_display':'variables',
878 'file_filter': file_filter,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500879 'build': build,
880 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500881 'objects' : variables,
882 'total_count':queryset_with_search.count(),
883 'default_orderby' : 'variable_name:+',
884 'search_term':search_term,
885 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
886 'tablecols' : [
887 {'name': 'Variable',
888 '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",
889 'orderfield': _get_toggle_order(request, "variable_name"),
890 'ordericon':_get_toggle_order_icon(request, "variable_name"),
891 },
892 {'name': 'Value',
893 'qhelp': "The value assigned to the variable",
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500894 },
895 {'name': 'Set in file',
896 'qhelp': "The last configuration file that touched the variable value",
897 'clclass': 'file', 'hidden' : 0,
898 'orderkey' : 'vhistory__file_name',
899 'filter' : {
900 'class' : 'vhistory__file_name',
901 'label': 'Show:',
902 'options' : [
903 ('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'),
904 ('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'),
905 ('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'),
906 ('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'),
907 ('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'),
908 ]
909 },
910 },
911 {'name': 'Description',
912 'qhelp': "A brief explanation of the variable",
913 'clclass': 'description', 'hidden' : 0,
914 'dclass': "span4",
915 'filter' : {
916 'class' : 'description',
917 'label': 'Show:',
918 'options' : [
919 ('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>'),
920 ]
921 },
922 },
923 ],
924 }
925
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500926 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500927 _set_parameters_values(pagesize, orderby, request)
928 return response
929
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500930def bfile(request, build_id, package_id):
931 template = 'bfile.html'
932 files = Package_File.objects.filter(package = package_id)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500933 build = Build.objects.get(pk=build_id)
934 context = {
935 'build': build,
936 'project': build.project,
937 'objects' : files
938 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500939 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500940
941
942# A set of dependency types valid for both included and built package views
943OTHER_DEPENDS_BASE = [
944 Package_Dependency.TYPE_RSUGGESTS,
945 Package_Dependency.TYPE_RPROVIDES,
946 Package_Dependency.TYPE_RREPLACES,
947 Package_Dependency.TYPE_RCONFLICTS,
948 ]
949
950# value for invalid row id
951INVALID_KEY = -1
952
953"""
954Given a package id, target_id retrieves two sets of this image and package's
955dependencies. The return value is a dictionary consisting of two other
956lists: a list of 'runtime' dependencies, that is, having RDEPENDS
957values in source package's recipe, and a list of other dependencies, that is
958the list of possible recipe variables as found in OTHER_DEPENDS_BASE plus
959the RRECOMMENDS or TRECOMMENDS value.
960The lists are built in the sort order specified for the package runtime
961dependency views.
962"""
963def _get_package_dependencies(package_id, target_id = INVALID_KEY):
964 runtime_deps = []
965 other_deps = []
966 other_depends_types = OTHER_DEPENDS_BASE
967
968 if target_id != INVALID_KEY :
969 rdepends_type = Package_Dependency.TYPE_TRDEPENDS
970 other_depends_types += [Package_Dependency.TYPE_TRECOMMENDS]
971 else :
972 rdepends_type = Package_Dependency.TYPE_RDEPENDS
973 other_depends_types += [Package_Dependency.TYPE_RRECOMMENDS]
974
975 package = Package.objects.get(pk=package_id)
976 if target_id != INVALID_KEY :
977 alldeps = package.package_dependencies_source.filter(target_id__exact = target_id)
978 else :
979 alldeps = package.package_dependencies_source.all()
980 for idep in alldeps:
981 dep_package = Package.objects.get(pk=idep.depends_on_id)
982 dep_entry = Package_Dependency.DEPENDS_DICT[idep.dep_type]
983 if dep_package.version == '' :
984 version = ''
985 else :
986 version = dep_package.version + "-" + dep_package.revision
987 installed = False
988 if target_id != INVALID_KEY :
989 if Target_Installed_Package.objects.filter(target_id__exact = target_id, package_id__exact = dep_package.id).count() > 0:
990 installed = True
991 dep = {
992 'name' : dep_package.name,
993 'version' : version,
994 'size' : dep_package.size,
995 'dep_type' : idep.dep_type,
996 'dep_type_display' : dep_entry[0].capitalize(),
997 'dep_type_help' : dep_entry[1] % (dep_package.name, package.name),
998 'depends_on_id' : dep_package.id,
999 'installed' : installed,
1000 }
1001
1002 if target_id != INVALID_KEY:
1003 dep['alias'] = _get_package_alias(dep_package)
1004
1005 if idep.dep_type == rdepends_type :
1006 runtime_deps.append(dep)
1007 elif idep.dep_type in other_depends_types :
1008 other_deps.append(dep)
1009
1010 rdep_sorted = sorted(runtime_deps, key=lambda k: k['name'])
1011 odep_sorted = sorted(
1012 sorted(other_deps, key=lambda k: k['name']),
1013 key=lambda k: k['dep_type'])
1014 retvalues = {'runtime_deps' : rdep_sorted, 'other_deps' : odep_sorted}
1015 return retvalues
1016
1017# Return the count of packages dependent on package for this target_id image
1018def _get_package_reverse_dep_count(package, target_id):
1019 return package.package_dependencies_target.filter(target_id__exact=target_id, dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1020
1021# Return the count of the packages that this package_id is dependent on.
1022# Use one of the two RDEPENDS types, either TRDEPENDS if the package was
1023# installed, or else RDEPENDS if only built.
1024def _get_package_dependency_count(package, target_id, is_installed):
1025 if is_installed :
1026 return package.package_dependencies_source.filter(target_id__exact = target_id,
1027 dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1028 else :
1029 return package.package_dependencies_source.filter(dep_type__exact = Package_Dependency.TYPE_RDEPENDS).count()
1030
1031def _get_package_alias(package):
1032 alias = package.installed_name
1033 if alias != None and alias != '' and alias != package.name:
1034 return alias
1035 else:
1036 return ''
1037
1038def _get_fullpackagespec(package):
1039 r = package.name
1040 version_good = package.version != None and package.version != ''
1041 revision_good = package.revision != None and package.revision != ''
1042 if version_good or revision_good:
1043 r += '_'
1044 if version_good:
1045 r += package.version
1046 if revision_good:
1047 r += '-'
1048 if revision_good:
1049 r += package.revision
1050 return r
1051
1052def package_built_detail(request, build_id, package_id):
1053 template = "package_built_detail.html"
1054 if Build.objects.filter(pk=build_id).count() == 0 :
1055 return redirect(builds)
1056
1057 # follow convention for pagination w/ search although not used for this view
1058 queryset = Package_File.objects.filter(package_id__exact=package_id)
1059 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1060 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1061 retval = _verify_parameters( request.GET, mandatory_parameters )
1062 if retval:
1063 return _redirect_parameters( 'package_built_detail', request.GET, mandatory_parameters, build_id = build_id, package_id = package_id)
1064
1065 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1066 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1067
1068 package = Package.objects.get(pk=package_id)
1069 package.fullpackagespec = _get_fullpackagespec(package)
1070 context = {
1071 'build' : Build.objects.get(pk=build_id),
1072 'package' : package,
1073 'dependency_count' : _get_package_dependency_count(package, -1, False),
1074 'objects' : paths,
1075 'tablecols':[
1076 {
1077 'name':'File',
1078 'orderfield': _get_toggle_order(request, "path"),
1079 'ordericon':_get_toggle_order_icon(request, "path"),
1080 },
1081 {
1082 'name':'Size',
1083 'orderfield': _get_toggle_order(request, "size", True),
1084 'ordericon':_get_toggle_order_icon(request, "size"),
1085 'dclass': 'sizecol span2',
1086 },
1087 ]
1088 }
1089 if paths.all().count() < 2:
1090 context['disable_sort'] = True;
1091
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001092 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001093 _set_parameters_values(pagesize, orderby, request)
1094 return response
1095
1096def package_built_dependencies(request, build_id, package_id):
1097 template = "package_built_dependencies.html"
1098 if Build.objects.filter(pk=build_id).count() == 0 :
1099 return redirect(builds)
1100
1101 package = Package.objects.get(pk=package_id)
1102 package.fullpackagespec = _get_fullpackagespec(package)
1103 dependencies = _get_package_dependencies(package_id)
1104 context = {
1105 'build' : Build.objects.get(pk=build_id),
1106 'package' : package,
1107 'runtime_deps' : dependencies['runtime_deps'],
1108 'other_deps' : dependencies['other_deps'],
1109 'dependency_count' : _get_package_dependency_count(package, -1, False)
1110 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001111 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001112
1113
1114def package_included_detail(request, build_id, target_id, package_id):
1115 template = "package_included_detail.html"
1116 if Build.objects.filter(pk=build_id).count() == 0 :
1117 return redirect(builds)
1118
1119 # follow convention for pagination w/ search although not used for this view
1120 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1121 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1122 retval = _verify_parameters( request.GET, mandatory_parameters )
1123 if retval:
1124 return _redirect_parameters( 'package_included_detail', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1125 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1126
1127 queryset = Package_File.objects.filter(package_id__exact=package_id)
1128 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1129
1130 package = Package.objects.get(pk=package_id)
1131 package.fullpackagespec = _get_fullpackagespec(package)
1132 package.alias = _get_package_alias(package)
1133 target = Target.objects.get(pk=target_id)
1134 context = {
1135 'build' : Build.objects.get(pk=build_id),
1136 'target' : target,
1137 'package' : package,
1138 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1139 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1140 'objects': paths,
1141 'tablecols':[
1142 {
1143 'name':'File',
1144 'orderfield': _get_toggle_order(request, "path"),
1145 'ordericon':_get_toggle_order_icon(request, "path"),
1146 },
1147 {
1148 'name':'Size',
1149 'orderfield': _get_toggle_order(request, "size", True),
1150 'ordericon':_get_toggle_order_icon(request, "size"),
1151 'dclass': 'sizecol span2',
1152 },
1153 ]
1154 }
1155 if paths.all().count() < 2:
1156 context['disable_sort'] = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001157 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001158 _set_parameters_values(pagesize, orderby, request)
1159 return response
1160
1161def package_included_dependencies(request, build_id, target_id, package_id):
1162 template = "package_included_dependencies.html"
1163 if Build.objects.filter(pk=build_id).count() == 0 :
1164 return redirect(builds)
1165
1166 package = Package.objects.get(pk=package_id)
1167 package.fullpackagespec = _get_fullpackagespec(package)
1168 package.alias = _get_package_alias(package)
1169 target = Target.objects.get(pk=target_id)
1170
1171 dependencies = _get_package_dependencies(package_id, target_id)
1172 context = {
1173 'build' : Build.objects.get(pk=build_id),
1174 'package' : package,
1175 'target' : target,
1176 'runtime_deps' : dependencies['runtime_deps'],
1177 'other_deps' : dependencies['other_deps'],
1178 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1179 'dependency_count' : _get_package_dependency_count(package, target_id, True)
1180 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001181 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001182
1183def package_included_reverse_dependencies(request, build_id, target_id, package_id):
1184 template = "package_included_reverse_dependencies.html"
1185 if Build.objects.filter(pk=build_id).count() == 0 :
1186 return redirect(builds)
1187
1188 (pagesize, orderby) = _get_parameters_values(request, 25, 'package__name:+')
1189 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
1190 retval = _verify_parameters( request.GET, mandatory_parameters )
1191 if retval:
1192 return _redirect_parameters( 'package_included_reverse_dependencies', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1193 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1194
1195 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)
1196 objects = _get_queryset(Package_Dependency, queryset, filter_string, search_term, ordering_string, 'package__name')
1197
1198 package = Package.objects.get(pk=package_id)
1199 package.fullpackagespec = _get_fullpackagespec(package)
1200 package.alias = _get_package_alias(package)
1201 target = Target.objects.get(pk=target_id)
1202 for o in objects:
1203 if o.package.version != '':
1204 o.package.version += '-' + o.package.revision
1205 o.alias = _get_package_alias(o.package)
1206 context = {
1207 'build' : Build.objects.get(pk=build_id),
1208 'package' : package,
1209 'target' : target,
1210 'objects' : objects,
1211 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1212 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1213 'tablecols':[
1214 {
1215 'name':'Package',
1216 'orderfield': _get_toggle_order(request, "package__name"),
1217 'ordericon': _get_toggle_order_icon(request, "package__name"),
1218 },
1219 {
1220 'name':'Version',
1221 },
1222 {
1223 'name':'Size',
1224 'orderfield': _get_toggle_order(request, "package__size", True),
1225 'ordericon': _get_toggle_order_icon(request, "package__size"),
1226 'dclass': 'sizecol span2',
1227 },
1228 ]
1229 }
1230 if objects.all().count() < 2:
1231 context['disable_sort'] = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001232 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001233 _set_parameters_values(pagesize, orderby, request)
1234 return response
1235
1236def image_information_dir(request, build_id, target_id, packagefile_id):
1237 # stubbed for now
1238 return redirect(builds)
1239 # the context processor that supplies data used across all the pages
1240
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001241# a context processor which runs on every request; this provides the
1242# projects and non_cli_projects (i.e. projects created by the user)
1243# variables referred to in templates, which used to determine the
1244# visibility of UI elements like the "New build" button
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001245def managedcontextprocessor(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001246 projects = Project.objects.all()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001247 ret = {
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001248 "projects": projects,
1249 "non_cli_projects": projects.exclude(is_default=True),
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001250 "DEBUG" : toastermain.settings.DEBUG,
1251 "TOASTER_BRANCH": toastermain.settings.TOASTER_BRANCH,
1252 "TOASTER_REVISION" : toastermain.settings.TOASTER_REVISION,
1253 }
1254 return ret
1255
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001256# REST-based API calls to return build/building status to external Toaster
1257# managers and aggregators via JSON
1258
1259def _json_build_status(build_id,extend):
1260 build_stat = None
1261 try:
1262 build = Build.objects.get( pk = build_id )
1263 build_stat = {}
1264 build_stat['id'] = build.id
1265 build_stat['name'] = build.build_name
1266 build_stat['machine'] = build.machine
1267 build_stat['distro'] = build.distro
1268 build_stat['start'] = build.started_on
1269 # look up target name
1270 target= Target.objects.get( build = build )
1271 if target:
1272 if target.task:
1273 build_stat['target'] = '%s:%s' % (target.target,target.task)
1274 else:
1275 build_stat['target'] = '%s' % (target.target)
1276 else:
1277 build_stat['target'] = ''
1278 # look up project name
1279 project = Project.objects.get( build = build )
1280 if project:
1281 build_stat['project'] = project.name
1282 else:
1283 build_stat['project'] = ''
1284 if Build.IN_PROGRESS == build.outcome:
1285 now = timezone.now()
1286 timediff = now - build.started_on
1287 build_stat['seconds']='%.3f' % timediff.total_seconds()
1288 build_stat['clone']='%d:%d' % (build.repos_cloned,build.repos_to_clone)
1289 build_stat['parse']='%d:%d' % (build.recipes_parsed,build.recipes_to_parse)
1290 tf = Task.objects.filter(build = build)
1291 tfc = tf.count()
1292 if tfc > 0:
1293 tfd = tf.exclude(order__isnull=True).count()
1294 else:
1295 tfd = 0
1296 build_stat['task']='%d:%d' % (tfd,tfc)
1297 else:
1298 build_stat['outcome'] = build.get_outcome_text()
1299 timediff = build.completed_on - build.started_on
1300 build_stat['seconds']='%.3f' % timediff.total_seconds()
1301 build_stat['stop'] = build.completed_on
1302 messages = LogMessage.objects.all().filter(build = build)
1303 errors = len(messages.filter(level=LogMessage.ERROR) |
1304 messages.filter(level=LogMessage.EXCEPTION) |
1305 messages.filter(level=LogMessage.CRITICAL))
1306 build_stat['errors'] = errors
1307 warnings = len(messages.filter(level=LogMessage.WARNING))
1308 build_stat['warnings'] = warnings
1309 if extend:
1310 build_stat['cooker_log'] = build.cooker_log_path
1311 except Exception as e:
1312 build_state = str(e)
1313 return build_stat
1314
1315def json_builds(request):
1316 build_table = []
1317 builds = []
1318 try:
1319 builds = Build.objects.exclude(outcome=Build.IN_PROGRESS).order_by("-started_on")
1320 for build in builds:
1321 build_table.append(_json_build_status(build.id,False))
1322 except Exception as e:
1323 build_table = str(e)
1324 return JsonResponse({'builds' : build_table, 'count' : len(builds)})
1325
1326def json_building(request):
1327 build_table = []
1328 builds = []
1329 try:
1330 builds = Build.objects.filter(outcome=Build.IN_PROGRESS).order_by("-started_on")
1331 for build in builds:
1332 build_table.append(_json_build_status(build.id,False))
1333 except Exception as e:
1334 build_table = str(e)
1335 return JsonResponse({'building' : build_table, 'count' : len(builds)})
1336
1337def json_build(request,build_id):
1338 return JsonResponse({'build' : _json_build_status(build_id,True)})
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001339
1340
1341import toastermain.settings
1342
1343from orm.models import Project, ProjectLayer, ProjectTarget, ProjectVariable
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001344from bldcontrol.models import BuildEnvironment
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001345
1346# we have a set of functions if we're in managed mode, or
1347# a default "page not available" simple functions for interactive mode
1348
1349if True:
1350 from django.contrib.auth.models import User
1351 from django.contrib.auth import authenticate, login
1352 from django.contrib.auth.decorators import login_required
1353
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001354 from orm.models import LayerSource, ToasterSetting, Release, Machine, LayerVersionDependency
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001355 from bldcontrol.models import BuildRequest
1356
1357 import traceback
1358
1359 class BadParameterException(Exception):
1360 ''' The exception raised on invalid POST requests '''
1361 pass
1362
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001363 # new project
1364 def newproject(request):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001365 if not project_enable:
1366 return redirect( landing )
1367
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001368 template = "newproject.html"
1369 context = {
1370 'email': request.user.email if request.user.is_authenticated() else '',
1371 'username': request.user.username if request.user.is_authenticated() else '',
1372 'releases': Release.objects.order_by("description"),
1373 }
1374
1375 try:
1376 context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
1377 except ToasterSetting.DoesNotExist:
1378 pass
1379
1380 if request.method == "GET":
1381 # render new project page
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001382 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001383 elif request.method == "POST":
1384 mandatory_fields = ['projectname', 'ptype']
1385 try:
1386 ptype = request.POST.get('ptype')
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001387 if ptype == "import":
1388 mandatory_fields.append('importdir')
1389 else:
1390 mandatory_fields.append('projectversion')
1391 # make sure we have values for all mandatory_fields
1392 missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0]
1393 if missing:
1394 # set alert for missing fields
1395 raise BadParameterException("Fields missing: %s" % ", ".join(missing))
1396
1397 if not request.user.is_authenticated():
1398 user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
1399 if user is None:
1400 user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
1401
1402 user = authenticate(username = user.username, password = 'nopass')
1403 login(request, user)
1404
1405 # save the project
1406 if ptype == "import":
1407 if not os.path.isdir('%s/conf' % request.POST['importdir']):
1408 raise BadParameterException("Bad path or missing 'conf' directory (%s)" % request.POST['importdir'])
1409 from django.core import management
1410 management.call_command('buildimport', '--command=import', '--name=%s' % request.POST['projectname'], '--path=%s' % request.POST['importdir'], interactive=False)
1411 prj = Project.objects.get(name = request.POST['projectname'])
1412 prj.merged_attr = True
1413 prj.save()
1414 else:
1415 release = Release.objects.get(pk = request.POST.get('projectversion', None ))
1416 prj = Project.objects.create_project(name = request.POST['projectname'], release = release)
1417 prj.user_id = request.user.pk
1418 if 'mergeattr' == request.POST.get('mergeattr', ''):
1419 prj.merged_attr = True
1420 prj.save()
1421
1422 return redirect(reverse(project, args=(prj.pk,)) + "?notify=new-project")
1423
1424 except (IntegrityError, BadParameterException) as e:
1425 # fill in page with previously submitted values
1426 for field in mandatory_fields:
1427 context.__setitem__(field, request.POST.get(field, "-- missing"))
1428 if isinstance(e, IntegrityError) and "username" in str(e):
1429 context['alert'] = "Your chosen username is already used"
1430 else:
1431 context['alert'] = str(e)
1432 return toaster_render(request, template, context)
1433
1434 raise Exception("Invalid HTTP method for this page")
1435
1436 # new project
1437 def newproject_specific(request, pid):
1438 if not project_enable:
1439 return redirect( landing )
1440
1441 project = Project.objects.get(pk=pid)
1442 template = "newproject_specific.html"
1443 context = {
1444 'email': request.user.email if request.user.is_authenticated() else '',
1445 'username': request.user.username if request.user.is_authenticated() else '',
1446 'releases': Release.objects.order_by("description"),
1447 'projectname': project.name,
1448 'project_pk': project.pk,
1449 }
1450
1451 # WORKAROUND: if we already know release, redirect 'newproject_specific' to 'project_specific'
1452 if '1' == project.get_variable('INTERNAL_PROJECT_SPECIFIC_SKIPRELEASE'):
1453 return redirect(reverse(project_specific, args=(project.pk,)))
1454
1455 try:
1456 context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
1457 except ToasterSetting.DoesNotExist:
1458 pass
1459
1460 if request.method == "GET":
1461 # render new project page
1462 return toaster_render(request, template, context)
1463 elif request.method == "POST":
1464 mandatory_fields = ['projectname', 'ptype']
1465 try:
1466 ptype = request.POST.get('ptype')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001467 if ptype == "build":
1468 mandatory_fields.append('projectversion')
1469 # make sure we have values for all mandatory_fields
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001470 missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0]
1471 if missing:
1472 # set alert for missing fields
1473 raise BadParameterException("Fields missing: %s" % ", ".join(missing))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001474
1475 if not request.user.is_authenticated():
1476 user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
1477 if user is None:
1478 user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
1479
1480 user = authenticate(username = user.username, password = 'nopass')
1481 login(request, user)
1482
1483 # save the project
1484 if ptype == "analysis":
1485 release = None
1486 else:
1487 release = Release.objects.get(pk = request.POST.get('projectversion', None ))
1488
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001489 prj = Project.objects.create_project(name = request.POST['projectname'], release = release, existing_project = project)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001490 prj.user_id = request.user.pk
1491 prj.save()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001492 return redirect(reverse(project_specific, args=(prj.pk,)) + "?notify=new-project")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001493
1494 except (IntegrityError, BadParameterException) as e:
1495 # fill in page with previously submitted values
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001496 for field in mandatory_fields:
1497 context.__setitem__(field, request.POST.get(field, "-- missing"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001498 if isinstance(e, IntegrityError) and "username" in str(e):
1499 context['alert'] = "Your chosen username is already used"
1500 else:
1501 context['alert'] = str(e)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001502 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001503
1504 raise Exception("Invalid HTTP method for this page")
1505
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001506 # Shows the edit project page
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001507 def project(request, pid):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001508 project = Project.objects.get(pk=pid)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001509
1510 if '1' == os.environ.get('TOASTER_PROJECTSPECIFIC'):
1511 if request.GET:
1512 #Example:request.GET=<QueryDict: {'setMachine': ['qemuarm']}>
1513 params = urlencode(request.GET).replace('%5B%27','').replace('%27%5D','')
1514 return redirect("%s?%s" % (reverse(project_specific, args=(project.pk,)),params))
1515 else:
1516 return redirect(reverse(project_specific, args=(project.pk,)))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001517 context = {"project": project}
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001518 return toaster_render(request, "project.html", context)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001519
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001520 # Shows the edit project-specific page
1521 def project_specific(request, pid):
1522 project = Project.objects.get(pk=pid)
1523
1524 # Are we refreshing from a successful project specific update clone?
1525 if Project.PROJECT_SPECIFIC_CLONING_SUCCESS == project.get_variable(Project.PROJECT_SPECIFIC_STATUS):
1526 return redirect(reverse(landing_specific,args=(project.pk,)))
1527
1528 context = {
1529 "project": project,
1530 "is_new" : project.get_variable(Project.PROJECT_SPECIFIC_ISNEW),
1531 "default_image_recipe" : project.get_variable(Project.PROJECT_SPECIFIC_DEFAULTIMAGE),
1532 "mru" : Build.objects.all().filter(project=project,outcome=Build.IN_PROGRESS),
1533 }
1534 if project.build_set.filter(outcome=Build.IN_PROGRESS).count() > 0:
1535 context['build_in_progress_none_completed'] = True
1536 else:
1537 context['build_in_progress_none_completed'] = False
1538 return toaster_render(request, "project.html", context)
1539
1540 # perform the final actions for the project specific page
1541 def project_specific_finalize(cmnd, pid):
1542 project = Project.objects.get(pk=pid)
1543 callback = project.get_variable(Project.PROJECT_SPECIFIC_CALLBACK)
1544 if "update" == cmnd:
1545 # Delete all '_PROJECT_PREPARE_' builds
1546 for b in Build.objects.all().filter(project=project):
1547 delete_build = False
1548 for t in b.target_set.all():
1549 if '_PROJECT_PREPARE_' == t.target:
1550 delete_build = True
1551 if delete_build:
1552 from django.core import management
1553 management.call_command('builddelete', str(b.id), interactive=False)
1554 # perform callback at this last moment if defined, in case Toaster gets shutdown next
1555 default_target = project.get_variable(Project.PROJECT_SPECIFIC_DEFAULTIMAGE)
1556 if callback:
1557 callback = callback.replace("<IMAGE>",default_target)
1558 if "cancel" == cmnd:
1559 if callback:
1560 callback = callback.replace("<IMAGE>","none")
1561 callback = callback.replace("--update","--cancel")
1562 # perform callback at this last moment if defined, in case this Toaster gets shutdown next
1563 ret = ''
1564 if callback:
1565 ret = os.system('bash -c "%s"' % callback)
1566 project.set_variable(Project.PROJECT_SPECIFIC_CALLBACK,'')
1567 # Delete the temp project specific variables
1568 project.set_variable(Project.PROJECT_SPECIFIC_ISNEW,'')
1569 project.set_variable(Project.PROJECT_SPECIFIC_STATUS,Project.PROJECT_SPECIFIC_NONE)
1570 # WORKAROUND: Release this workaround flag
1571 project.set_variable('INTERNAL_PROJECT_SPECIFIC_SKIPRELEASE','')
1572
1573 # Shows the final landing page for project specific update
1574 def landing_specific(request, pid):
1575 project_specific_finalize("update", pid)
1576 context = {
1577 "install_dir": os.environ['TOASTER_DIR'],
1578 }
1579 return toaster_render(request, "landing_specific.html", context)
1580
1581 # Shows the related landing-specific page
1582 def landing_specific_cancel(request, pid):
1583 project_specific_finalize("cancel", pid)
1584 context = {
1585 "install_dir": os.environ['TOASTER_DIR'],
1586 "status": "cancel",
1587 }
1588 return toaster_render(request, "landing_specific.html", context)
1589
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001590 def jsunittests(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001591 """ Provides a page for the js unit tests """
1592 bbv = BitbakeVersion.objects.filter(branch="master").first()
1593 release = Release.objects.filter(bitbake_version=bbv).first()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001594
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001595 name = "_js_unit_test_prj_"
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001596
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001597 # If there is an existing project by this name delete it.
1598 # We don't want Lots of duplicates cluttering up the projects.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001599 Project.objects.filter(name=name).delete()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001600
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001601 new_project = Project.objects.create_project(name=name,
1602 release=release)
1603 # Add a layer
1604 layer = new_project.get_all_compatible_layer_versions().first()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001605
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001606 ProjectLayer.objects.get_or_create(layercommit=layer,
1607 project=new_project)
1608
1609 # make sure we have a machine set for this project
1610 ProjectVariable.objects.get_or_create(project=new_project,
1611 name="MACHINE",
1612 value="qemux86")
1613 context = {'project': new_project}
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001614 return toaster_render(request, "js-unit-tests.html", context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001615
1616 from django.views.decorators.csrf import csrf_exempt
1617 @csrf_exempt
1618 def xhr_testreleasechange(request, pid):
1619 def response(data):
1620 return HttpResponse(jsonfilter(data),
1621 content_type="application/json")
1622
1623 """ returns layer versions that would be deleted on the new
1624 release__pk """
1625 try:
1626 prj = Project.objects.get(pk = pid)
1627 new_release_id = request.GET['new_release_id']
1628
1629 # If we're already on this project do nothing
1630 if prj.release.pk == int(new_release_id):
1631 return reponse({"error": "ok", "rows": []})
1632
1633 retval = []
1634
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001635 for project in prj.projectlayer_set.all():
1636 release = Release.objects.get(pk = new_release_id)
1637
1638 layer_versions = prj.get_all_compatible_layer_versions()
1639 layer_versions = layer_versions.filter(release = release)
1640 layer_versions = layer_versions.filter(layer__name = project.layercommit.layer.name)
1641
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001642 # there is no layer_version with the new release id,
1643 # and the same name
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001644 if layer_versions.count() < 1:
1645 retval.append(project)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001646
1647 return response({"error":"ok",
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001648 "rows": [_lv_to_dict(prj) for y in [x.layercommit for x in retval]]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001649 })
1650
1651 except Exception as e:
1652 return response({"error": str(e) })
1653
1654 def xhr_configvaredit(request, pid):
1655 try:
1656 prj = Project.objects.get(id = pid)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001657 # There are cases where user can add variables which hold values
1658 # like http://, file:/// etc. In such case a simple split(":")
1659 # would fail. One example is SSTATE_MIRRORS variable. So we use
1660 # max_split var to handle them.
1661 max_split = 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001662 # add conf variables
1663 if 'configvarAdd' in request.POST:
1664 t=request.POST['configvarAdd'].strip()
1665 if ":" in t:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001666 variable, value = t.split(":", max_split)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001667 else:
1668 variable = t
1669 value = ""
1670
1671 pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable, value = value)
1672 # change conf variables
1673 if 'configvarChange' in request.POST:
1674 t=request.POST['configvarChange'].strip()
1675 if ":" in t:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001676 variable, value = t.split(":", max_split)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001677 else:
1678 variable = t
1679 value = ""
1680
1681 pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable)
1682 pt.value=value
1683 pt.save()
1684 # remove conf variables
1685 if 'configvarDel' in request.POST:
1686 t=request.POST['configvarDel'].strip()
1687 pt = ProjectVariable.objects.get(pk = int(t)).delete()
1688
1689 # return all project settings, filter out blacklist and elsewhere-managed variables
1690 vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context()
1691 configvars_query = ProjectVariable.objects.filter(project_id = pid).all()
1692 for var in vars_managed:
1693 configvars_query = configvars_query.exclude(name = var)
1694 for var in vars_blacklist:
1695 configvars_query = configvars_query.exclude(name = var)
1696
1697 return_data = {
1698 "error": "ok",
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001699 'configvars': [(x.name, x.value, x.pk) for x in configvars_query]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001700 }
1701 try:
1702 return_data['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value,
1703 except ProjectVariable.DoesNotExist:
1704 pass
1705 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001706 return_data['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value,
1707 except ProjectVariable.DoesNotExist:
1708 pass
1709 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001710 return_data['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value,
1711 except ProjectVariable.DoesNotExist:
1712 pass
1713 try:
1714 return_data['image_install_append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL_append").value,
1715 except ProjectVariable.DoesNotExist:
1716 pass
1717 try:
1718 return_data['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value,
1719 except ProjectVariable.DoesNotExist:
1720 pass
1721 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001722 return_data['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001723 except ProjectVariable.DoesNotExist:
1724 pass
1725
1726 return HttpResponse(json.dumps( return_data ), content_type = "application/json")
1727
1728 except Exception as e:
1729 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1730
1731
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001732 def customrecipe_download(request, pid, recipe_id):
1733 recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id)
1734
1735 file_data = recipe.generate_recipe_file_contents()
1736
1737 response = HttpResponse(file_data, content_type='text/plain')
1738 response['Content-Disposition'] = \
1739 'attachment; filename="%s_%s.bb"' % (recipe.name,
1740 recipe.version)
1741
1742 return response
1743
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001744 def importlayer(request, pid):
1745 template = "importlayer.html"
1746 context = {
1747 'project': Project.objects.get(id=pid),
1748 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001749 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001750
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001751 def layerdetails(request, pid, layerid):
1752 project = Project.objects.get(pk=pid)
1753 layer_version = Layer_Version.objects.get(pk=layerid)
1754
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001755 project_layers = ProjectLayer.objects.filter(
1756 project=project).values_list("layercommit_id",
1757 flat=True)
1758
1759 context = {
1760 'project': project,
1761 'layer_source': LayerSource.types_dict(),
1762 'layerversion': layer_version,
1763 'layerdeps': {
1764 "list": [
1765 {
1766 "id": dep.id,
1767 "name": dep.layer.name,
1768 "layerdetailurl": reverse('layerdetails',
1769 args=(pid, dep.pk)),
1770 "vcs_url": dep.layer.vcs_url,
1771 "vcs_reference": dep.get_vcs_reference()
1772 }
1773 for dep in layer_version.get_alldeps(project.id)]
1774 },
1775 'projectlayers': list(project_layers)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001776 }
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001777
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001778 return toaster_render(request, 'layerdetails.html', context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001779
1780
1781 def get_project_configvars_context():
1782 # Vars managed outside of this view
1783 vars_managed = {
1784 'MACHINE', 'BBLAYERS'
1785 }
1786
1787 vars_blacklist = {
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001788 'PARALLEL_MAKE','BB_NUMBER_THREADS',
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001789 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001790 'PARALLEL_MAKE','TMPDIR',
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001791 'all_proxy','ftp_proxy','http_proxy ','https_proxy'
1792 }
1793
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001794 vars_fstypes = Target_Image_File.SUFFIXES
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001795
1796 return(vars_managed,sorted(vars_fstypes),vars_blacklist)
1797
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001798 def projectconf(request, pid):
1799
1800 try:
1801 prj = Project.objects.get(id = pid)
1802 except Project.DoesNotExist:
1803 return HttpResponseNotFound("<h1>Project id " + pid + " is unavailable</h1>")
1804
1805 # remove blacklist and externally managed varaibles from this list
1806 vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context()
1807 configvars = ProjectVariable.objects.filter(project_id = pid).all()
1808 for var in vars_managed:
1809 configvars = configvars.exclude(name = var)
1810 for var in vars_blacklist:
1811 configvars = configvars.exclude(name = var)
1812
1813 context = {
1814 'project': prj,
1815 'configvars': configvars,
1816 'vars_managed': vars_managed,
1817 'vars_fstypes': vars_fstypes,
1818 'vars_blacklist': vars_blacklist,
1819 }
1820
1821 try:
1822 context['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value
1823 context['distro_defined'] = "1"
1824 except ProjectVariable.DoesNotExist:
1825 pass
1826 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001827 if ProjectVariable.objects.get(project = prj, name = "DL_DIR").value == "${TOPDIR}/../downloads":
1828 be = BuildEnvironment.objects.get(pk = str(1))
1829 dl_dir = os.path.join(dirname(be.builddir), "downloads")
1830 context['dl_dir'] = dl_dir
1831 pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "DL_DIR")
1832 pv.value = dl_dir
1833 pv.save()
1834 else:
1835 context['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value
1836 context['dl_dir_defined'] = "1"
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001837 except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001838 pass
1839 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001840 context['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value
1841 context['fstypes_defined'] = "1"
1842 except ProjectVariable.DoesNotExist:
1843 pass
1844 try:
1845 context['image_install_append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL_append").value
1846 context['image_install_append_defined'] = "1"
1847 except ProjectVariable.DoesNotExist:
1848 pass
1849 try:
1850 context['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value
1851 context['package_classes_defined'] = "1"
1852 except ProjectVariable.DoesNotExist:
1853 pass
1854 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001855 if ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value == "${TOPDIR}/../sstate-cache":
1856 be = BuildEnvironment.objects.get(pk = str(1))
1857 sstate_dir = os.path.join(dirname(be.builddir), "sstate-cache")
1858 context['sstate_dir'] = sstate_dir
1859 pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "SSTATE_DIR")
1860 pv.value = sstate_dir
1861 pv.save()
1862 else:
1863 context['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value
1864 context['sstate_dir_defined'] = "1"
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001865 except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001866 pass
1867
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001868 return toaster_render(request, "projectconf.html", context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001869
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001870 def _file_names_for_artifact(build, artifact_type, artifact_id):
1871 """
1872 Return a tuple (file path, file name for the download response) for an
1873 artifact of type artifact_type with ID artifact_id for build; if
1874 artifact type is not supported, returns (None, None)
1875 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001876 file_name = None
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001877 response_file_name = None
1878
1879 if artifact_type == "cookerlog":
1880 file_name = build.cooker_log_path
1881 response_file_name = "cooker.log"
1882
1883 elif artifact_type == "imagefile":
1884 file_name = Target_Image_File.objects.get(target__build = build, pk = artifact_id).file_name
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001885
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001886 elif artifact_type == "targetkernelartifact":
1887 target = TargetKernelFile.objects.get(pk=artifact_id)
1888 file_name = target.file_name
1889
1890 elif artifact_type == "targetsdkartifact":
1891 target = TargetSDKFile.objects.get(pk=artifact_id)
1892 file_name = target.file_name
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001893
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001894 elif artifact_type == "licensemanifest":
1895 file_name = Target.objects.get(build = build, pk = artifact_id).license_manifest_path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001896
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001897 elif artifact_type == "packagemanifest":
1898 file_name = Target.objects.get(build = build, pk = artifact_id).package_manifest_path
1899
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001900 elif artifact_type == "tasklogfile":
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001901 file_name = Task.objects.get(build = build, pk = artifact_id).logfile
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001902
1903 elif artifact_type == "logmessagefile":
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001904 file_name = LogMessage.objects.get(build = build, pk = artifact_id).pathname
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001905
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001906 if file_name and not response_file_name:
1907 response_file_name = os.path.basename(file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001908
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001909 return (file_name, response_file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001910
1911 def build_artifact(request, build_id, artifact_type, artifact_id):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001912 """
1913 View which returns a build artifact file as a response
1914 """
1915 file_name = None
1916 response_file_name = None
1917
1918 try:
1919 build = Build.objects.get(pk = build_id)
1920 file_name, response_file_name = _file_names_for_artifact(
1921 build, artifact_type, artifact_id
1922 )
1923
1924 if file_name and response_file_name:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001925 fsock = open(file_name, "rb")
Patrick Williamsd7e96312015-09-22 08:09:05 -05001926 content_type = MimeTypeFinder.get_mimetype(file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001927
1928 response = HttpResponse(fsock, content_type = content_type)
1929
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001930 disposition = "attachment; filename=" + response_file_name
1931 response["Content-Disposition"] = disposition
Patrick Williamsd7e96312015-09-22 08:09:05 -05001932
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001933 return response
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001934 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001935 return toaster_render(request, "unavailable_artifact.html")
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001936 except (ObjectDoesNotExist, IOError):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001937 return toaster_render(request, "unavailable_artifact.html")
1938