blob: 34ed2b2e3c4bad9eb7085b8d3ff27e9fb0d9a9e2 [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
Patrick Williamsc124f4f2015-09-15 14:41:29 -050022
Patrick Williamsc0f7c042017-02-23 20:41:17 -060023import re
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024
Patrick Williamsc0f7c042017-02-23 20:41:17 -060025from django.db.models import F, Q, Sum
26from django.db import IntegrityError
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050027from django.shortcuts import render, redirect, get_object_or_404
Patrick Williamsc0f7c042017-02-23 20:41:17 -060028from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe
29from orm.models import LogMessage, Variable, Package_Dependency, Package
30from orm.models import Task_Dependency, Package_File
31from orm.models import Target_Installed_Package, Target_File
32from orm.models import TargetKernelFile, TargetSDKFile, Target_Image_File
Patrick Williamsf1e5d692016-03-30 15:21:19 -050033from orm.models import BitbakeVersion, CustomImageRecipe
Patrick Williamsc0f7c042017-02-23 20:41:17 -060034
Patrick Williamsc124f4f2015-09-15 14:41:29 -050035from django.core.urlresolvers import reverse, resolve
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050036from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
Patrick Williamsc124f4f2015-09-15 14:41:29 -050037from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
Brad Bishopd7bf8c12018-02-25 22:55:05 -050038from django.http import HttpResponseNotFound, JsonResponse
Patrick Williamsc124f4f2015-09-15 14:41:29 -050039from django.utils import timezone
Patrick Williamsd7e96312015-09-22 08:09:05 -050040from datetime import timedelta, datetime
Patrick Williamsc124f4f2015-09-15 14:41:29 -050041from toastergui.templatetags.projecttags import json as jsonfilter
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050042from decimal import Decimal
Patrick Williamsc124f4f2015-09-15 14:41:29 -050043import json
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050044import os
Patrick Williamsc124f4f2015-09-15 14:41:29 -050045from os.path import dirname
Patrick Williamsf1e5d692016-03-30 15:21:19 -050046import mimetypes
Patrick Williamsc124f4f2015-09-15 14:41:29 -050047
48import logging
49
50logger = logging.getLogger("toaster")
51
Brad Bishopd7bf8c12018-02-25 22:55:05 -050052# Project creation and managed build enable
53project_enable = ('1' == os.environ.get('TOASTER_BUILDSERVER'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -060054
Patrick Williamsd7e96312015-09-22 08:09:05 -050055class MimeTypeFinder(object):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050056 # setting this to False enables additional non-standard mimetypes
57 # to be included in the guess
58 _strict = False
Patrick Williamsd7e96312015-09-22 08:09:05 -050059
Patrick Williamsf1e5d692016-03-30 15:21:19 -050060 # returns the mimetype for a file path as a string,
61 # or 'application/octet-stream' if the type couldn't be guessed
Patrick Williamsd7e96312015-09-22 08:09:05 -050062 @classmethod
63 def get_mimetype(self, path):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050064 guess = mimetypes.guess_type(path, self._strict)
65 guessed_type = guess[0]
66 if guessed_type == None:
67 guessed_type = 'application/octet-stream'
68 return guessed_type
Patrick Williamsd7e96312015-09-22 08:09:05 -050069
Brad Bishopd7bf8c12018-02-25 22:55:05 -050070# single point to add global values into the context before rendering
71def toaster_render(request, page, context):
72 context['project_enable'] = project_enable
73 return render(request, page, context)
74
75
Patrick Williamsc124f4f2015-09-15 14:41:29 -050076# all new sessions should come through the landing page;
77# determine in which mode we are running in, and redirect appropriately
78def landing(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050079 # in build mode, we redirect to the command-line builds page
80 # if there are any builds for the default (cli builds) project
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050081 default_project = Project.objects.get_or_create_default_project()
Patrick Williamsf1e5d692016-03-30 15:21:19 -050082 default_project_builds = Build.objects.filter(project = default_project)
83
Patrick Williamsc124f4f2015-09-15 14:41:29 -050084 # we only redirect to projects page if there is a user-generated project
Patrick Williamsf1e5d692016-03-30 15:21:19 -050085 num_builds = Build.objects.all().count()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050086 user_projects = Project.objects.filter(is_default = False)
87 has_user_project = user_projects.count() > 0
88
Patrick Williamsf1e5d692016-03-30 15:21:19 -050089 if num_builds == 0 and has_user_project:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050090 return redirect(reverse('all-projects'), permanent = False)
91
Patrick Williamsf1e5d692016-03-30 15:21:19 -050092 if num_builds > 0:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050093 return redirect(reverse('all-builds'), permanent = False)
94
95 context = {'lvs_nos' : Layer_Version.objects.all().count()}
96
Brad Bishopd7bf8c12018-02-25 22:55:05 -050097 return toaster_render(request, 'landing.html', context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050098
Patrick Williamsc124f4f2015-09-15 14:41:29 -050099def objtojson(obj):
100 from django.db.models.query import QuerySet
101 from django.db.models import Model
102
103 if isinstance(obj, datetime):
104 return obj.isoformat()
105 elif isinstance(obj, timedelta):
106 return obj.total_seconds()
107 elif isinstance(obj, QuerySet) or isinstance(obj, set):
108 return list(obj)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500109 elif isinstance(obj, Decimal):
110 return str(obj)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500111 elif type(obj).__name__ == "RelatedManager":
112 return [x.pk for x in obj.all()]
113 elif hasattr( obj, '__dict__') and isinstance(obj, Model):
114 d = obj.__dict__
115 nd = dict(d)
116 for di in d.keys():
117 if di.startswith("_"):
118 del nd[di]
119 elif isinstance(d[di], Model):
120 nd[di] = d[di].pk
121 elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di):
122 nd[di] = getattr(obj, "get_%s_display" % di)()
123 return nd
124 elif isinstance( obj, type(lambda x:x)):
125 import inspect
126 return inspect.getsourcelines(obj)[0]
127 else:
128 raise TypeError("Unserializable object %s (%s) of type %s" % ( obj, dir(obj), type(obj)))
129
130
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500131def _lv_to_dict(prj, x = None):
132 if x is None:
133 def wrapper(x):
134 return _lv_to_dict(prj, x)
135 return wrapper
136
137 return {"id": x.pk,
138 "name": x.layer.name,
139 "tooltip": "%s | %s" % (x.layer.vcs_url,x.get_vcs_reference()),
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600140 "detail": "(%s" % x.layer.vcs_url + (")" if x.release == None else " | "+x.get_vcs_reference()+")"),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500141 "giturl": x.layer.vcs_url,
142 "layerdetailurl" : reverse('layerdetails', args=(prj.id,x.pk)),
143 "revision" : x.get_vcs_reference(),
144 }
145
146
147def _build_page_range(paginator, index = 1):
148 try:
149 page = paginator.page(index)
150 except PageNotAnInteger:
151 page = paginator.page(1)
152 except EmptyPage:
153 page = paginator.page(paginator.num_pages)
154
155
156 page.page_range = [page.number]
157 crt_range = 0
158 for i in range(1,5):
159 if (page.number + i) <= paginator.num_pages:
160 page.page_range = page.page_range + [ page.number + i]
161 crt_range +=1
162 if (page.number - i) > 0:
163 page.page_range = [page.number -i] + page.page_range
164 crt_range +=1
165 if crt_range == 4:
166 break
167 return page
168
169
170def _verify_parameters(g, mandatory_parameters):
171 miss = []
172 for mp in mandatory_parameters:
173 if not mp in g:
174 miss.append(mp)
175 if len(miss):
176 return miss
177 return None
178
179def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600180 try:
181 from urllib import unquote, urlencode
182 except ImportError:
183 from urllib.parse import unquote, urlencode
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500184 url = reverse(view, kwargs=kwargs)
185 params = {}
186 for i in g:
187 params[i] = g[i]
188 for i in mandatory_parameters:
189 if not i in params:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600190 params[i] = unquote(str(mandatory_parameters[i]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500191
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600192 return redirect(url + "?%s" % urlencode(params), permanent = False, **kwargs)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500193
194class RedirectException(Exception):
195 def __init__(self, view, g, mandatory_parameters, *args, **kwargs):
196 super(RedirectException, self).__init__()
197 self.view = view
198 self.g = g
199 self.mandatory_parameters = mandatory_parameters
200 self.oargs = args
201 self.okwargs = kwargs
202
203 def get_redirect_response(self):
204 return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, **self.okwargs)
205
206FIELD_SEPARATOR = ":"
207AND_VALUE_SEPARATOR = "!"
208OR_VALUE_SEPARATOR = "|"
209DESCENDING = "-"
210
211def __get_q_for_val(name, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600212 if "OR" in value or "AND" in value:
213 result = None
214 for x in value.split("OR"):
215 x = __get_q_for_val(name, x)
216 result = result | x if result else x
217 return result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500218 if "AND" in value:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600219 result = None
220 for x in value.split("AND"):
221 x = __get_q_for_val(name, x)
222 result = result & x if result else x
223 return result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500224 if value.startswith("NOT"):
225 value = value[3:]
226 if value == 'None':
227 value = None
228 kwargs = { name : value }
229 return ~Q(**kwargs)
230 else:
231 if value == 'None':
232 value = None
233 kwargs = { name : value }
234 return Q(**kwargs)
235
236def _get_filtering_query(filter_string):
237
238 search_terms = filter_string.split(FIELD_SEPARATOR)
239 and_keys = search_terms[0].split(AND_VALUE_SEPARATOR)
240 and_values = search_terms[1].split(AND_VALUE_SEPARATOR)
241
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600242 and_query = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500243 for kv in zip(and_keys, and_values):
244 or_keys = kv[0].split(OR_VALUE_SEPARATOR)
245 or_values = kv[1].split(OR_VALUE_SEPARATOR)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600246 query = None
247 for key, val in zip(or_keys, or_values):
248 x = __get_q_for_val(key, val)
249 query = query | x if query else x
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500250
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600251 and_query = and_query & query if and_query else query
252
253 return and_query
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254
255def _get_toggle_order(request, orderkey, toggle_reverse = False):
256 if toggle_reverse:
257 return "%s:+" % orderkey if request.GET.get('orderby', "") == "%s:-" % orderkey else "%s:-" % orderkey
258 else:
259 return "%s:-" % orderkey if request.GET.get('orderby', "") == "%s:+" % orderkey else "%s:+" % orderkey
260
261def _get_toggle_order_icon(request, orderkey):
262 if request.GET.get('orderby', "") == "%s:+"%orderkey:
263 return "down"
264 elif request.GET.get('orderby', "") == "%s:-"%orderkey:
265 return "up"
266 else:
267 return None
268
269# we check that the input comes in a valid form that we can recognize
270def _validate_input(field_input, model):
271
272 invalid = None
273
274 if field_input:
275 field_input_list = field_input.split(FIELD_SEPARATOR)
276
277 # Check we have only one colon
278 if len(field_input_list) != 2:
279 invalid = "We have an invalid number of separators: " + field_input + " -> " + str(field_input_list)
280 return None, invalid
281
282 # Check we have an equal number of terms both sides of the colon
283 if len(field_input_list[0].split(AND_VALUE_SEPARATOR)) != len(field_input_list[1].split(AND_VALUE_SEPARATOR)):
284 invalid = "Not all arg names got values"
285 return None, invalid + str(field_input_list)
286
287 # Check we are looking for a valid field
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500288 valid_fields = [f.name for f in model._meta.get_fields()]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500289 for field in field_input_list[0].split(AND_VALUE_SEPARATOR):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600290 if True in [field.startswith(x) for x in valid_fields]:
291 break
292 else:
293 return None, (field, valid_fields)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500294
295 return field_input, invalid
296
297# uses search_allowed_fields in orm/models.py to create a search query
298# for these fields with the supplied input text
299def _get_search_results(search_term, queryset, model):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600300 search_object = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500301 for st in search_term.split(" "):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600302 queries = None
303 for field in model.search_allowed_fields:
304 query = Q(**{field + '__icontains': st})
305 queries = queries | query if queries else query
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500306
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600307 search_object = search_object & queries if search_object else queries
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500308 queryset = queryset.filter(search_object)
309
310 return queryset
311
312
313# function to extract the search/filter/ordering parameters from the request
314# it uses the request and the model to validate input for the filter and orderby values
315def _search_tuple(request, model):
316 ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model)
317 if invalid:
318 raise BaseException("Invalid ordering model:" + str(model) + str(invalid))
319
320 filter_string, invalid = _validate_input(request.GET.get('filter', ''), model)
321 if invalid:
322 raise BaseException("Invalid filter " + str(invalid))
323
324 search_term = request.GET.get('search', '')
325 return (filter_string, search_term, ordering_string)
326
327
328# returns a lazy-evaluated queryset for a filter/search/order combination
329def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''):
330 if filter_string:
331 filter_query = _get_filtering_query(filter_string)
332 queryset = queryset.filter(filter_query)
333 else:
334 queryset = queryset.all()
335
336 if search_term:
337 queryset = _get_search_results(search_term, queryset, model)
338
339 if ordering_string:
340 column, order = ordering_string.split(':')
341 if column == re.sub('-','',ordering_secondary):
342 ordering_secondary=''
343 if order.lower() == DESCENDING:
344 column = '-' + column
345 if ordering_secondary:
346 queryset = queryset.order_by(column, ordering_secondary)
347 else:
348 queryset = queryset.order_by(column)
349
350 # insure only distinct records (e.g. from multiple search hits) are returned
351 return queryset.distinct()
352
353# returns the value of entries per page and the name of the applied sorting field.
354# if the value is given explicitly as a GET parameter it will be the first selected,
355# otherwise the cookie value will be used.
356def _get_parameters_values(request, default_count, default_order):
357 current_url = resolve(request.path_info).url_name
358 pagesize = request.GET.get('count', request.session.get('%s_count' % current_url, default_count))
359 orderby = request.GET.get('orderby', request.session.get('%s_orderby' % current_url, default_order))
360 return (pagesize, orderby)
361
362
363# set cookies for parameters. this is usefull in case parameters are set
364# manually from the GET values of the link
365def _set_parameters_values(pagesize, orderby, request):
366 from django.core.urlresolvers import resolve
367 current_url = resolve(request.path_info).url_name
368 request.session['%s_count' % current_url] = pagesize
369 request.session['%s_orderby' % current_url] =orderby
370
371# date range: normalize GUI's dd/mm/yyyy to date object
372def _normalize_input_date(date_str,default):
373 date_str=re.sub('/', '-', date_str)
374 # accept dd/mm/yyyy to d/m/yy
375 try:
376 date_in = datetime.strptime(date_str, "%d-%m-%Y")
377 except ValueError:
378 # courtesy try with two digit year
379 try:
380 date_in = datetime.strptime(date_str, "%d-%m-%y")
381 except ValueError:
382 return default
383 date_in = date_in.replace(tzinfo=default.tzinfo)
384 return date_in
385
386# convert and normalize any received date range filter, for example:
387# "completed_on__gte!completed_on__lt:01/03/2015!02/03/2015_daterange" to
388# "completed_on__gte!completed_on__lt:2015-03-01!2015-03-02"
389def _modify_date_range_filter(filter_string):
390 # was the date range radio button selected?
391 if 0 > filter_string.find('_daterange'):
392 return filter_string,''
393 # normalize GUI dates to database format
394 filter_string = filter_string.replace('_daterange','').replace(':','!');
395 filter_list = filter_string.split('!');
396 if 4 != len(filter_list):
397 return filter_string
398 today = timezone.localtime(timezone.now())
399 date_id = filter_list[1]
400 date_from = _normalize_input_date(filter_list[2],today)
401 date_to = _normalize_input_date(filter_list[3],today)
402 # swap dates if manually set dates are out of order
403 if date_to < date_from:
404 date_to,date_from = date_from,date_to
405 # convert to strings, make 'date_to' inclusive by moving to begining of next day
406 date_from_str = date_from.strftime("%Y-%m-%d")
407 date_to_str = (date_to+timedelta(days=1)).strftime("%Y-%m-%d")
408 filter_string=filter_list[0]+'!'+filter_list[1]+':'+date_from_str+'!'+date_to_str
409 daterange_selected = re.sub('__.*','', date_id)
410 return filter_string,daterange_selected
411
412def _add_daterange_context(queryset_all, request, daterange_list):
413 # calculate the exact begining of local today and yesterday
414 today_begin = timezone.localtime(timezone.now())
Patrick Williamsd7e96312015-09-22 08:09:05 -0500415 yesterday_begin = today_begin - timedelta(days=1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500416 # add daterange persistent
417 context_date = {}
418 context_date['last_date_from'] = request.GET.get('last_date_from',timezone.localtime(timezone.now()).strftime("%d/%m/%Y"))
419 context_date['last_date_to' ] = request.GET.get('last_date_to' ,context_date['last_date_from'])
420 # calculate the date ranges, avoid second sort for 'created'
421 # fetch the respective max range from the database
422 context_date['daterange_filter']=''
423 for key in daterange_list:
424 queryset_key = queryset_all.order_by(key)
425 try:
426 context_date['dateMin_'+key]=timezone.localtime(getattr(queryset_key.first(),key)).strftime("%d/%m/%Y")
427 except AttributeError:
428 context_date['dateMin_'+key]=timezone.localtime(timezone.now())
429 try:
430 context_date['dateMax_'+key]=timezone.localtime(getattr(queryset_key.last(),key)).strftime("%d/%m/%Y")
431 except AttributeError:
432 context_date['dateMax_'+key]=timezone.localtime(timezone.now())
433 return context_date,today_begin,yesterday_begin
434
435
436##
437# build dashboard for a single build, coming in as argument
438# Each build may contain multiple targets and each target
439# may generate multiple image files. display them all.
440#
441def builddashboard( request, build_id ):
442 template = "builddashboard.html"
443 if Build.objects.filter( pk=build_id ).count( ) == 0 :
444 return redirect( builds )
445 build = Build.objects.get( pk = build_id );
446 layerVersionId = Layer_Version.objects.filter( build = build_id );
447 recipeCount = Recipe.objects.filter( layer_version__id__in = layerVersionId ).count( );
448 tgts = Target.objects.filter( build_id = build_id ).order_by( 'target' );
449
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500450 # set up custom target list with computed package and image data
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600451 targets = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500452 ntargets = 0
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600453
454 # True if at least one target for this build has an SDK artifact
455 # or image file
456 has_artifacts = False
457
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500458 for t in tgts:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600459 elem = {}
460 elem['target'] = t
461
462 target_has_images = False
463 image_files = []
464
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500465 npkg = 0
466 pkgsz = 0
467 package = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500468 # Chunk the query to avoid "too many SQL variables" error
469 package_set = t.target_installed_package_set.all()
470 package_set_len = len(package_set)
471 for ps_start in range(0,package_set_len,500):
472 ps_stop = min(ps_start+500,package_set_len)
473 for package in Package.objects.filter(id__in = [x.package_id for x in package_set[ps_start:ps_stop]]):
474 pkgsz = pkgsz + package.size
475 if package.installed_name:
476 npkg = npkg + 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600477 elem['npkg'] = npkg
478 elem['pkgsz'] = pkgsz
479 ti = Target_Image_File.objects.filter(target_id = t.id)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500480 for i in ti:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600481 ndx = i.file_name.rfind('/')
482 if ndx < 0:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500483 ndx = 0;
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600484 f = i.file_name[ndx + 1:]
485 image_files.append({
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500486 'id': i.id,
487 'path': f,
488 'size': i.file_size,
489 'suffix': i.suffix
490 })
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600491 if len(image_files) > 0:
492 target_has_images = True
493 elem['targetHasImages'] = target_has_images
494
495 elem['imageFiles'] = image_files
496 elem['target_kernel_artifacts'] = t.targetkernelfile_set.all()
497
498 target_sdk_files = t.targetsdkfile_set.all()
499 target_sdk_artifacts_count = target_sdk_files.count()
500 elem['target_sdk_artifacts_count'] = target_sdk_artifacts_count
501 elem['target_sdk_artifacts'] = target_sdk_files
502
503 if target_has_images or target_sdk_artifacts_count > 0:
504 has_artifacts = True
505
506 targets.append(elem)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500507
508 ##
509 # how many packages in this build - ignore anonymous ones
510 #
511
512 packageCount = 0
513 packages = Package.objects.filter( build_id = build_id )
514 for p in packages:
515 if ( p.installed_name ):
516 packageCount = packageCount + 1
517
518 logmessages = list(LogMessage.objects.filter( build = build_id ))
519
520 context = {
521 'build' : build,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500522 'project' : build.project,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600523 'hasArtifacts' : has_artifacts,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500524 'ntargets' : ntargets,
525 'targets' : targets,
526 'recipecount' : recipeCount,
527 'packagecount' : packageCount,
528 'logmessages' : logmessages,
529 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500530 return toaster_render( request, template, context )
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500531
532
533
534def generateCoveredList2( revlist = None ):
535 if not revlist:
536 revlist = []
537 covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
538 while len(covered_list):
539 revlist = [ x for x in revlist if x.outcome != Task.OUTCOME_COVERED ]
540 if len(revlist) > 0:
541 return revlist
542
543 newlist = _find_task_revdep_list(covered_list)
544
545 revlist = list(set(revlist + newlist))
546 covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
547 return revlist
548
549def task( request, build_id, task_id ):
550 template = "task.html"
551 tasks_list = Task.objects.filter( pk=task_id )
552 if tasks_list.count( ) == 0:
553 return redirect( builds )
554 task_object = tasks_list[ 0 ];
555 dependencies = sorted(
556 _find_task_dep( task_object ),
557 key=lambda t:'%s_%s %s'%(t.recipe.name, t.recipe.version, t.task_name))
558 reverse_dependencies = sorted(
559 _find_task_revdep( task_object ),
560 key=lambda t:'%s_%s %s'%( t.recipe.name, t.recipe.version, t.task_name ))
561 coveredBy = '';
562 if ( task_object.outcome == Task.OUTCOME_COVERED ):
563# _list = generateCoveredList( task )
564 coveredBy = sorted(generateCoveredList2( _find_task_revdep( task_object ) ), key = lambda x: x.recipe.name)
565 log_head = ''
566 log_body = ''
567 if task_object.outcome == task_object.OUTCOME_FAILED:
568 pass
569
570 uri_list= [ ]
571 variables = Variable.objects.filter(build=build_id)
572 v=variables.filter(variable_name='SSTATE_DIR')
573 if v.count() > 0:
574 uri_list.append(v[0].variable_value)
575 v=variables.filter(variable_name='SSTATE_MIRRORS')
576 if (v.count() > 0):
577 for mirror in v[0].variable_value.split('\\n'):
578 s=re.sub('.* ','',mirror.strip(' \t\n\r'))
579 if len(s):
580 uri_list.append(s)
581
582 context = {
583 'build' : Build.objects.filter( pk = build_id )[ 0 ],
584 'object' : task_object,
585 'task' : task_object,
586 'covered_by' : coveredBy,
587 'deps' : dependencies,
588 'rdeps' : reverse_dependencies,
589 'log_head' : log_head,
590 'log_body' : log_body,
591 'showing_matches' : False,
592 'uri_list' : uri_list,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600593 'task_in_tasks_table_pg': int(task_object.order / 25) + 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500594 }
595 if request.GET.get( 'show_matches', "" ):
596 context[ 'showing_matches' ] = True
597 context[ 'matching_tasks' ] = Task.objects.filter(
598 sstate_checksum=task_object.sstate_checksum ).filter(
599 build__completed_on__lt=task_object.build.completed_on).exclude(
600 order__isnull=True).exclude(outcome=Task.OUTCOME_NA).order_by('-build__completed_on')
601
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500602 return toaster_render( request, template, context )
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500603
604def recipe(request, build_id, recipe_id, active_tab="1"):
605 template = "recipe.html"
606 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
607 return redirect(builds)
608
609 recipe_object = Recipe.objects.get(pk=recipe_id)
610 layer_version = Layer_Version.objects.get(pk=recipe_object.layer_version_id)
611 layer = Layer.objects.get(pk=layer_version.layer_id)
612 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)
613 package_count = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0).count()
614
615 if active_tab != '1' and active_tab != '3' and active_tab != '4' :
616 active_tab = '1'
617 tab_states = {'1': '', '3': '', '4': ''}
618 tab_states[active_tab] = 'active'
619
620 context = {
621 'build' : Build.objects.get(pk=build_id),
622 'object' : recipe_object,
623 'layer_version' : layer_version,
624 'layer' : layer,
625 'tasks' : tasks_list,
626 'package_count' : package_count,
627 'tab_states' : tab_states,
628 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500629 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500630
631def recipe_packages(request, build_id, recipe_id):
632 template = "recipe_packages.html"
633 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
634 return redirect(builds)
635
636 (pagesize, orderby) = _get_parameters_values(request, 10, 'name:+')
637 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
638 retval = _verify_parameters( request.GET, mandatory_parameters )
639 if retval:
640 return _redirect_parameters( 'recipe_packages', request.GET, mandatory_parameters, build_id = build_id, recipe_id = recipe_id)
641 (filter_string, search_term, ordering_string) = _search_tuple(request, Package)
642
643 recipe_object = Recipe.objects.get(pk=recipe_id)
644 queryset = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0)
645 package_count = queryset.count()
646 queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name')
647
648 packages = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
649
650 context = {
651 'build' : Build.objects.get(pk=build_id),
652 'recipe' : recipe_object,
653 'objects' : packages,
654 'object_count' : package_count,
655 'tablecols':[
656 {
657 'name':'Package',
658 'orderfield': _get_toggle_order(request,"name"),
659 'ordericon': _get_toggle_order_icon(request,"name"),
660 'orderkey': "name",
661 },
662 {
663 'name':'Version',
664 },
665 {
666 'name':'Size',
667 'orderfield': _get_toggle_order(request,"size", True),
668 'ordericon': _get_toggle_order_icon(request,"size"),
669 'orderkey': 'size',
670 'dclass': 'sizecol span2',
671 },
672 ]
673 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500674 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500675 _set_parameters_values(pagesize, orderby, request)
676 return response
677
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500678from django.core.serializers.json import DjangoJSONEncoder
679from django.http import HttpResponse
680def xhr_dirinfo(request, build_id, target_id):
681 top = request.GET.get('start', '/')
682 return HttpResponse(_get_dir_entries(build_id, target_id, top), content_type = "application/json")
683
684from django.utils.functional import Promise
685from django.utils.encoding import force_text
686class LazyEncoder(json.JSONEncoder):
687 def default(self, obj):
688 if isinstance(obj, Promise):
689 return force_text(obj)
690 return super(LazyEncoder, self).default(obj)
691
692from toastergui.templatetags.projecttags import filtered_filesizeformat
693import os
694def _get_dir_entries(build_id, target_id, start):
695 node_str = {
696 Target_File.ITYPE_REGULAR : '-',
697 Target_File.ITYPE_DIRECTORY : 'd',
698 Target_File.ITYPE_SYMLINK : 'l',
699 Target_File.ITYPE_SOCKET : 's',
700 Target_File.ITYPE_FIFO : 'p',
701 Target_File.ITYPE_CHARACTER : 'c',
702 Target_File.ITYPE_BLOCK : 'b',
703 }
704 response = []
705 objects = Target_File.objects.filter(target__exact=target_id, directory__path=start)
706 target_packages = Target_Installed_Package.objects.filter(target__exact=target_id).values_list('package_id', flat=True)
707 for o in objects:
708 # exclude root inode '/'
709 if o.path == '/':
710 continue
711 try:
712 entry = {}
713 entry['parent'] = start
714 entry['name'] = os.path.basename(o.path)
715 entry['fullpath'] = o.path
716
717 # set defaults, not all dentries have packages
718 entry['installed_package'] = None
719 entry['package_id'] = None
720 entry['package'] = None
721 entry['link_to'] = None
722 if o.inodetype == Target_File.ITYPE_DIRECTORY:
723 entry['isdir'] = 1
724 # is there content in directory
725 entry['childcount'] = Target_File.objects.filter(target__exact=target_id, directory__path=o.path).all().count()
726 else:
727 entry['isdir'] = 0
728
729 # resolve the file to get the package from the resolved file
730 resolved_id = o.sym_target_id
731 resolved_path = o.path
732 if target_packages.count():
733 while resolved_id != "" and resolved_id != None:
734 tf = Target_File.objects.get(pk=resolved_id)
735 resolved_path = tf.path
736 resolved_id = tf.sym_target_id
737
738 thisfile=Package_File.objects.all().filter(path__exact=resolved_path, package_id__in=target_packages)
739 if thisfile.count():
740 p = Package.objects.get(pk=thisfile[0].package_id)
741 entry['installed_package'] = p.installed_name
742 entry['package_id'] = str(p.id)
743 entry['package'] = p.name
744 # don't use resolved path from above, show immediate link-to
745 if o.sym_target_id != "" and o.sym_target_id != None:
746 entry['link_to'] = Target_File.objects.get(pk=o.sym_target_id).path
747 entry['size'] = filtered_filesizeformat(o.size)
748 if entry['link_to'] != None:
749 entry['permission'] = node_str[o.inodetype] + o.permission
750 else:
751 entry['permission'] = node_str[o.inodetype] + o.permission
752 entry['owner'] = o.owner
753 entry['group'] = o.group
754 response.append(entry)
755
756 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600757 print("Exception ", e)
758 traceback.print_exc()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500759
760 # sort by directories first, then by name
761 rsorted = sorted(response, key=lambda entry : entry['name'])
762 rsorted = sorted(rsorted, key=lambda entry : entry['isdir'], reverse=True)
763 return json.dumps(rsorted, cls=LazyEncoder).replace('</', '<\\/')
764
765def dirinfo(request, build_id, target_id, file_path=None):
766 template = "dirinfo.html"
767 objects = _get_dir_entries(build_id, target_id, '/')
768 packages_sum = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')).aggregate(Sum('installed_size'))
769 dir_list = None
770 if file_path != None:
771 """
772 Link from the included package detail file list page and is
773 requesting opening the dir info to a specific file path.
774 Provide the list of directories to expand and the full path to
775 highlight in the page.
776 """
777 # Aassume target's path separator matches host's, that is, os.sep
778 sep = os.sep
779 dir_list = []
780 head = file_path
781 while head != sep:
782 (head, tail) = os.path.split(head)
783 if head != sep:
784 dir_list.insert(0, head)
785
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500786 build = Build.objects.get(pk=build_id)
787
788 context = { 'build': build,
789 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500790 'target': Target.objects.get(pk=target_id),
791 'packages_sum': packages_sum['installed_size__sum'],
792 'objects': objects,
793 'dir_list': dir_list,
794 'file_path': file_path,
795 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500796 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500797
798def _find_task_dep(task_object):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600799 tdeps = Task_Dependency.objects.filter(task=task_object).filter(depends_on__order__gt=0)
800 tdeps = tdeps.exclude(depends_on__outcome=Task.OUTCOME_NA).select_related("depends_on")
801 return [x.depends_on for x in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500802
803def _find_task_revdep(task_object):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600804 tdeps = Task_Dependency.objects.filter(depends_on=task_object).filter(task__order__gt=0)
805 tdeps = tdeps.exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
806
807 # exclude self-dependencies to prevent infinite dependency loop
808 # in generateCoveredList2()
809 tdeps = tdeps.exclude(task=task_object)
810
811 return [tdep.task for tdep in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500812
813def _find_task_revdep_list(tasklist):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600814 tdeps = Task_Dependency.objects.filter(depends_on__in=tasklist).filter(task__order__gt=0)
815 tdeps = tdeps.exclude(task__outcome=Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
816
817 # exclude self-dependencies to prevent infinite dependency loop
818 # in generateCoveredList2()
819 tdeps = tdeps.exclude(task=F('depends_on'))
820
821 return [tdep.task for tdep in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500822
823def _find_task_provider(task_object):
824 task_revdeps = _find_task_revdep(task_object)
825 for tr in task_revdeps:
826 if tr.outcome != Task.OUTCOME_COVERED:
827 return tr
828 for tr in task_revdeps:
829 trc = _find_task_provider(tr)
830 if trc is not None:
831 return trc
832 return None
833
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500834def configuration(request, build_id):
835 template = 'configuration.html'
836
837 var_names = ('BB_VERSION', 'BUILD_SYS', 'NATIVELSBSTRING', 'TARGET_SYS',
838 'MACHINE', 'DISTRO', 'DISTRO_VERSION', 'TUNE_FEATURES', 'TARGET_FPU')
839 context = dict(Variable.objects.filter(build=build_id, variable_name__in=var_names)\
840 .values_list('variable_name', 'variable_value'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500841 build = Build.objects.get(pk=build_id)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500842 context.update({'objectname': 'configuration',
843 'object_search_display':'variables',
844 'filter_search_display':'variables',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500845 'build': build,
846 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500847 'targets': Target.objects.filter(build=build_id)})
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500848 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500849
850
851def configvars(request, build_id):
852 template = 'configvars.html'
853 (pagesize, orderby) = _get_parameters_values(request, 100, 'variable_name:+')
854 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby, 'filter' : 'description__regex:.+' }
855 retval = _verify_parameters( request.GET, mandatory_parameters )
856 (filter_string, search_term, ordering_string) = _search_tuple(request, Variable)
857 if retval:
858 # if new search, clear the default filter
859 if search_term and len(search_term):
860 mandatory_parameters['filter']=''
861 return _redirect_parameters( 'configvars', request.GET, mandatory_parameters, build_id = build_id)
862
863 queryset = Variable.objects.filter(build=build_id).exclude(variable_name__istartswith='B_').exclude(variable_name__istartswith='do_')
864 queryset_with_search = _get_queryset(Variable, queryset, None, search_term, ordering_string, 'variable_name').exclude(variable_value='',vhistory__file_name__isnull=True)
865 queryset = _get_queryset(Variable, queryset, filter_string, search_term, ordering_string, 'variable_name')
866 # remove records where the value is empty AND there are no history files
867 queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True)
868
869 variables = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
870
871 # show all matching files (not just the last one)
872 file_filter= search_term + ":"
873 if filter_string.find('/conf/') > 0:
874 file_filter += 'conf/(local|bblayers).conf'
875 if filter_string.find('conf/machine/') > 0:
876 file_filter += 'conf/machine/'
877 if filter_string.find('conf/distro/') > 0:
878 file_filter += 'conf/distro/'
879 if filter_string.find('/bitbake.conf') > 0:
880 file_filter += '/bitbake.conf'
881 build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path)
882
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500883 build = Build.objects.get(pk=build_id)
884
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500885 context = {
886 'objectname': 'configvars',
887 'object_search_display':'BitBake variables',
888 'filter_search_display':'variables',
889 'file_filter': file_filter,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500890 'build': build,
891 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500892 'objects' : variables,
893 'total_count':queryset_with_search.count(),
894 'default_orderby' : 'variable_name:+',
895 'search_term':search_term,
896 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
897 'tablecols' : [
898 {'name': 'Variable',
899 '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",
900 'orderfield': _get_toggle_order(request, "variable_name"),
901 'ordericon':_get_toggle_order_icon(request, "variable_name"),
902 },
903 {'name': 'Value',
904 'qhelp': "The value assigned to the variable",
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500905 },
906 {'name': 'Set in file',
907 'qhelp': "The last configuration file that touched the variable value",
908 'clclass': 'file', 'hidden' : 0,
909 'orderkey' : 'vhistory__file_name',
910 'filter' : {
911 'class' : 'vhistory__file_name',
912 'label': 'Show:',
913 'options' : [
914 ('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'),
915 ('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'),
916 ('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'),
917 ('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'),
918 ('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'),
919 ]
920 },
921 },
922 {'name': 'Description',
923 'qhelp': "A brief explanation of the variable",
924 'clclass': 'description', 'hidden' : 0,
925 'dclass': "span4",
926 'filter' : {
927 'class' : 'description',
928 'label': 'Show:',
929 'options' : [
930 ('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>'),
931 ]
932 },
933 },
934 ],
935 }
936
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500937 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500938 _set_parameters_values(pagesize, orderby, request)
939 return response
940
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500941def bfile(request, build_id, package_id):
942 template = 'bfile.html'
943 files = Package_File.objects.filter(package = package_id)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500944 build = Build.objects.get(pk=build_id)
945 context = {
946 'build': build,
947 'project': build.project,
948 'objects' : files
949 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500950 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500951
952
953# A set of dependency types valid for both included and built package views
954OTHER_DEPENDS_BASE = [
955 Package_Dependency.TYPE_RSUGGESTS,
956 Package_Dependency.TYPE_RPROVIDES,
957 Package_Dependency.TYPE_RREPLACES,
958 Package_Dependency.TYPE_RCONFLICTS,
959 ]
960
961# value for invalid row id
962INVALID_KEY = -1
963
964"""
965Given a package id, target_id retrieves two sets of this image and package's
966dependencies. The return value is a dictionary consisting of two other
967lists: a list of 'runtime' dependencies, that is, having RDEPENDS
968values in source package's recipe, and a list of other dependencies, that is
969the list of possible recipe variables as found in OTHER_DEPENDS_BASE plus
970the RRECOMMENDS or TRECOMMENDS value.
971The lists are built in the sort order specified for the package runtime
972dependency views.
973"""
974def _get_package_dependencies(package_id, target_id = INVALID_KEY):
975 runtime_deps = []
976 other_deps = []
977 other_depends_types = OTHER_DEPENDS_BASE
978
979 if target_id != INVALID_KEY :
980 rdepends_type = Package_Dependency.TYPE_TRDEPENDS
981 other_depends_types += [Package_Dependency.TYPE_TRECOMMENDS]
982 else :
983 rdepends_type = Package_Dependency.TYPE_RDEPENDS
984 other_depends_types += [Package_Dependency.TYPE_RRECOMMENDS]
985
986 package = Package.objects.get(pk=package_id)
987 if target_id != INVALID_KEY :
988 alldeps = package.package_dependencies_source.filter(target_id__exact = target_id)
989 else :
990 alldeps = package.package_dependencies_source.all()
991 for idep in alldeps:
992 dep_package = Package.objects.get(pk=idep.depends_on_id)
993 dep_entry = Package_Dependency.DEPENDS_DICT[idep.dep_type]
994 if dep_package.version == '' :
995 version = ''
996 else :
997 version = dep_package.version + "-" + dep_package.revision
998 installed = False
999 if target_id != INVALID_KEY :
1000 if Target_Installed_Package.objects.filter(target_id__exact = target_id, package_id__exact = dep_package.id).count() > 0:
1001 installed = True
1002 dep = {
1003 'name' : dep_package.name,
1004 'version' : version,
1005 'size' : dep_package.size,
1006 'dep_type' : idep.dep_type,
1007 'dep_type_display' : dep_entry[0].capitalize(),
1008 'dep_type_help' : dep_entry[1] % (dep_package.name, package.name),
1009 'depends_on_id' : dep_package.id,
1010 'installed' : installed,
1011 }
1012
1013 if target_id != INVALID_KEY:
1014 dep['alias'] = _get_package_alias(dep_package)
1015
1016 if idep.dep_type == rdepends_type :
1017 runtime_deps.append(dep)
1018 elif idep.dep_type in other_depends_types :
1019 other_deps.append(dep)
1020
1021 rdep_sorted = sorted(runtime_deps, key=lambda k: k['name'])
1022 odep_sorted = sorted(
1023 sorted(other_deps, key=lambda k: k['name']),
1024 key=lambda k: k['dep_type'])
1025 retvalues = {'runtime_deps' : rdep_sorted, 'other_deps' : odep_sorted}
1026 return retvalues
1027
1028# Return the count of packages dependent on package for this target_id image
1029def _get_package_reverse_dep_count(package, target_id):
1030 return package.package_dependencies_target.filter(target_id__exact=target_id, dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1031
1032# Return the count of the packages that this package_id is dependent on.
1033# Use one of the two RDEPENDS types, either TRDEPENDS if the package was
1034# installed, or else RDEPENDS if only built.
1035def _get_package_dependency_count(package, target_id, is_installed):
1036 if is_installed :
1037 return package.package_dependencies_source.filter(target_id__exact = target_id,
1038 dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1039 else :
1040 return package.package_dependencies_source.filter(dep_type__exact = Package_Dependency.TYPE_RDEPENDS).count()
1041
1042def _get_package_alias(package):
1043 alias = package.installed_name
1044 if alias != None and alias != '' and alias != package.name:
1045 return alias
1046 else:
1047 return ''
1048
1049def _get_fullpackagespec(package):
1050 r = package.name
1051 version_good = package.version != None and package.version != ''
1052 revision_good = package.revision != None and package.revision != ''
1053 if version_good or revision_good:
1054 r += '_'
1055 if version_good:
1056 r += package.version
1057 if revision_good:
1058 r += '-'
1059 if revision_good:
1060 r += package.revision
1061 return r
1062
1063def package_built_detail(request, build_id, package_id):
1064 template = "package_built_detail.html"
1065 if Build.objects.filter(pk=build_id).count() == 0 :
1066 return redirect(builds)
1067
1068 # follow convention for pagination w/ search although not used for this view
1069 queryset = Package_File.objects.filter(package_id__exact=package_id)
1070 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1071 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1072 retval = _verify_parameters( request.GET, mandatory_parameters )
1073 if retval:
1074 return _redirect_parameters( 'package_built_detail', request.GET, mandatory_parameters, build_id = build_id, package_id = package_id)
1075
1076 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1077 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1078
1079 package = Package.objects.get(pk=package_id)
1080 package.fullpackagespec = _get_fullpackagespec(package)
1081 context = {
1082 'build' : Build.objects.get(pk=build_id),
1083 'package' : package,
1084 'dependency_count' : _get_package_dependency_count(package, -1, False),
1085 'objects' : paths,
1086 'tablecols':[
1087 {
1088 'name':'File',
1089 'orderfield': _get_toggle_order(request, "path"),
1090 'ordericon':_get_toggle_order_icon(request, "path"),
1091 },
1092 {
1093 'name':'Size',
1094 'orderfield': _get_toggle_order(request, "size", True),
1095 'ordericon':_get_toggle_order_icon(request, "size"),
1096 'dclass': 'sizecol span2',
1097 },
1098 ]
1099 }
1100 if paths.all().count() < 2:
1101 context['disable_sort'] = True;
1102
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001103 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001104 _set_parameters_values(pagesize, orderby, request)
1105 return response
1106
1107def package_built_dependencies(request, build_id, package_id):
1108 template = "package_built_dependencies.html"
1109 if Build.objects.filter(pk=build_id).count() == 0 :
1110 return redirect(builds)
1111
1112 package = Package.objects.get(pk=package_id)
1113 package.fullpackagespec = _get_fullpackagespec(package)
1114 dependencies = _get_package_dependencies(package_id)
1115 context = {
1116 'build' : Build.objects.get(pk=build_id),
1117 'package' : package,
1118 'runtime_deps' : dependencies['runtime_deps'],
1119 'other_deps' : dependencies['other_deps'],
1120 'dependency_count' : _get_package_dependency_count(package, -1, False)
1121 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001122 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001123
1124
1125def package_included_detail(request, build_id, target_id, package_id):
1126 template = "package_included_detail.html"
1127 if Build.objects.filter(pk=build_id).count() == 0 :
1128 return redirect(builds)
1129
1130 # follow convention for pagination w/ search although not used for this view
1131 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1132 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1133 retval = _verify_parameters( request.GET, mandatory_parameters )
1134 if retval:
1135 return _redirect_parameters( 'package_included_detail', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1136 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1137
1138 queryset = Package_File.objects.filter(package_id__exact=package_id)
1139 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1140
1141 package = Package.objects.get(pk=package_id)
1142 package.fullpackagespec = _get_fullpackagespec(package)
1143 package.alias = _get_package_alias(package)
1144 target = Target.objects.get(pk=target_id)
1145 context = {
1146 'build' : Build.objects.get(pk=build_id),
1147 'target' : target,
1148 'package' : package,
1149 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1150 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1151 'objects': paths,
1152 'tablecols':[
1153 {
1154 'name':'File',
1155 'orderfield': _get_toggle_order(request, "path"),
1156 'ordericon':_get_toggle_order_icon(request, "path"),
1157 },
1158 {
1159 'name':'Size',
1160 'orderfield': _get_toggle_order(request, "size", True),
1161 'ordericon':_get_toggle_order_icon(request, "size"),
1162 'dclass': 'sizecol span2',
1163 },
1164 ]
1165 }
1166 if paths.all().count() < 2:
1167 context['disable_sort'] = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001168 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001169 _set_parameters_values(pagesize, orderby, request)
1170 return response
1171
1172def package_included_dependencies(request, build_id, target_id, package_id):
1173 template = "package_included_dependencies.html"
1174 if Build.objects.filter(pk=build_id).count() == 0 :
1175 return redirect(builds)
1176
1177 package = Package.objects.get(pk=package_id)
1178 package.fullpackagespec = _get_fullpackagespec(package)
1179 package.alias = _get_package_alias(package)
1180 target = Target.objects.get(pk=target_id)
1181
1182 dependencies = _get_package_dependencies(package_id, target_id)
1183 context = {
1184 'build' : Build.objects.get(pk=build_id),
1185 'package' : package,
1186 'target' : target,
1187 'runtime_deps' : dependencies['runtime_deps'],
1188 'other_deps' : dependencies['other_deps'],
1189 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1190 'dependency_count' : _get_package_dependency_count(package, target_id, True)
1191 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001192 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001193
1194def package_included_reverse_dependencies(request, build_id, target_id, package_id):
1195 template = "package_included_reverse_dependencies.html"
1196 if Build.objects.filter(pk=build_id).count() == 0 :
1197 return redirect(builds)
1198
1199 (pagesize, orderby) = _get_parameters_values(request, 25, 'package__name:+')
1200 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
1201 retval = _verify_parameters( request.GET, mandatory_parameters )
1202 if retval:
1203 return _redirect_parameters( 'package_included_reverse_dependencies', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1204 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1205
1206 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)
1207 objects = _get_queryset(Package_Dependency, queryset, filter_string, search_term, ordering_string, 'package__name')
1208
1209 package = Package.objects.get(pk=package_id)
1210 package.fullpackagespec = _get_fullpackagespec(package)
1211 package.alias = _get_package_alias(package)
1212 target = Target.objects.get(pk=target_id)
1213 for o in objects:
1214 if o.package.version != '':
1215 o.package.version += '-' + o.package.revision
1216 o.alias = _get_package_alias(o.package)
1217 context = {
1218 'build' : Build.objects.get(pk=build_id),
1219 'package' : package,
1220 'target' : target,
1221 'objects' : objects,
1222 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1223 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1224 'tablecols':[
1225 {
1226 'name':'Package',
1227 'orderfield': _get_toggle_order(request, "package__name"),
1228 'ordericon': _get_toggle_order_icon(request, "package__name"),
1229 },
1230 {
1231 'name':'Version',
1232 },
1233 {
1234 'name':'Size',
1235 'orderfield': _get_toggle_order(request, "package__size", True),
1236 'ordericon': _get_toggle_order_icon(request, "package__size"),
1237 'dclass': 'sizecol span2',
1238 },
1239 ]
1240 }
1241 if objects.all().count() < 2:
1242 context['disable_sort'] = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001243 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001244 _set_parameters_values(pagesize, orderby, request)
1245 return response
1246
1247def image_information_dir(request, build_id, target_id, packagefile_id):
1248 # stubbed for now
1249 return redirect(builds)
1250 # the context processor that supplies data used across all the pages
1251
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001252# a context processor which runs on every request; this provides the
1253# projects and non_cli_projects (i.e. projects created by the user)
1254# variables referred to in templates, which used to determine the
1255# visibility of UI elements like the "New build" button
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001256def managedcontextprocessor(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001257 projects = Project.objects.all()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001258 ret = {
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001259 "projects": projects,
1260 "non_cli_projects": projects.exclude(is_default=True),
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001261 "DEBUG" : toastermain.settings.DEBUG,
1262 "TOASTER_BRANCH": toastermain.settings.TOASTER_BRANCH,
1263 "TOASTER_REVISION" : toastermain.settings.TOASTER_REVISION,
1264 }
1265 return ret
1266
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001267# REST-based API calls to return build/building status to external Toaster
1268# managers and aggregators via JSON
1269
1270def _json_build_status(build_id,extend):
1271 build_stat = None
1272 try:
1273 build = Build.objects.get( pk = build_id )
1274 build_stat = {}
1275 build_stat['id'] = build.id
1276 build_stat['name'] = build.build_name
1277 build_stat['machine'] = build.machine
1278 build_stat['distro'] = build.distro
1279 build_stat['start'] = build.started_on
1280 # look up target name
1281 target= Target.objects.get( build = build )
1282 if target:
1283 if target.task:
1284 build_stat['target'] = '%s:%s' % (target.target,target.task)
1285 else:
1286 build_stat['target'] = '%s' % (target.target)
1287 else:
1288 build_stat['target'] = ''
1289 # look up project name
1290 project = Project.objects.get( build = build )
1291 if project:
1292 build_stat['project'] = project.name
1293 else:
1294 build_stat['project'] = ''
1295 if Build.IN_PROGRESS == build.outcome:
1296 now = timezone.now()
1297 timediff = now - build.started_on
1298 build_stat['seconds']='%.3f' % timediff.total_seconds()
1299 build_stat['clone']='%d:%d' % (build.repos_cloned,build.repos_to_clone)
1300 build_stat['parse']='%d:%d' % (build.recipes_parsed,build.recipes_to_parse)
1301 tf = Task.objects.filter(build = build)
1302 tfc = tf.count()
1303 if tfc > 0:
1304 tfd = tf.exclude(order__isnull=True).count()
1305 else:
1306 tfd = 0
1307 build_stat['task']='%d:%d' % (tfd,tfc)
1308 else:
1309 build_stat['outcome'] = build.get_outcome_text()
1310 timediff = build.completed_on - build.started_on
1311 build_stat['seconds']='%.3f' % timediff.total_seconds()
1312 build_stat['stop'] = build.completed_on
1313 messages = LogMessage.objects.all().filter(build = build)
1314 errors = len(messages.filter(level=LogMessage.ERROR) |
1315 messages.filter(level=LogMessage.EXCEPTION) |
1316 messages.filter(level=LogMessage.CRITICAL))
1317 build_stat['errors'] = errors
1318 warnings = len(messages.filter(level=LogMessage.WARNING))
1319 build_stat['warnings'] = warnings
1320 if extend:
1321 build_stat['cooker_log'] = build.cooker_log_path
1322 except Exception as e:
1323 build_state = str(e)
1324 return build_stat
1325
1326def json_builds(request):
1327 build_table = []
1328 builds = []
1329 try:
1330 builds = Build.objects.exclude(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({'builds' : build_table, 'count' : len(builds)})
1336
1337def json_building(request):
1338 build_table = []
1339 builds = []
1340 try:
1341 builds = Build.objects.filter(outcome=Build.IN_PROGRESS).order_by("-started_on")
1342 for build in builds:
1343 build_table.append(_json_build_status(build.id,False))
1344 except Exception as e:
1345 build_table = str(e)
1346 return JsonResponse({'building' : build_table, 'count' : len(builds)})
1347
1348def json_build(request,build_id):
1349 return JsonResponse({'build' : _json_build_status(build_id,True)})
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001350
1351
1352import toastermain.settings
1353
1354from orm.models import Project, ProjectLayer, ProjectTarget, ProjectVariable
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001355from bldcontrol.models import BuildEnvironment
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001356
1357# we have a set of functions if we're in managed mode, or
1358# a default "page not available" simple functions for interactive mode
1359
1360if True:
1361 from django.contrib.auth.models import User
1362 from django.contrib.auth import authenticate, login
1363 from django.contrib.auth.decorators import login_required
1364
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001365 from orm.models import LayerSource, ToasterSetting, Release, Machine, LayerVersionDependency
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001366 from bldcontrol.models import BuildRequest
1367
1368 import traceback
1369
1370 class BadParameterException(Exception):
1371 ''' The exception raised on invalid POST requests '''
1372 pass
1373
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001374 # new project
1375 def newproject(request):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001376 if not project_enable:
1377 return redirect( landing )
1378
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001379 template = "newproject.html"
1380 context = {
1381 'email': request.user.email if request.user.is_authenticated() else '',
1382 'username': request.user.username if request.user.is_authenticated() else '',
1383 'releases': Release.objects.order_by("description"),
1384 }
1385
1386 try:
1387 context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
1388 except ToasterSetting.DoesNotExist:
1389 pass
1390
1391 if request.method == "GET":
1392 # render new project page
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001393 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001394 elif request.method == "POST":
1395 mandatory_fields = ['projectname', 'ptype']
1396 try:
1397 ptype = request.POST.get('ptype')
1398 if ptype == "build":
1399 mandatory_fields.append('projectversion')
1400 # make sure we have values for all mandatory_fields
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001401 missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0]
1402 if missing:
1403 # set alert for missing fields
1404 raise BadParameterException("Fields missing: %s" % ", ".join(missing))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001405
1406 if not request.user.is_authenticated():
1407 user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
1408 if user is None:
1409 user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
1410
1411 user = authenticate(username = user.username, password = 'nopass')
1412 login(request, user)
1413
1414 # save the project
1415 if ptype == "analysis":
1416 release = None
1417 else:
1418 release = Release.objects.get(pk = request.POST.get('projectversion', None ))
1419
1420 prj = Project.objects.create_project(name = request.POST['projectname'], release = release)
1421 prj.user_id = request.user.pk
1422 prj.save()
1423 return redirect(reverse(project, args=(prj.pk,)) + "?notify=new-project")
1424
1425 except (IntegrityError, BadParameterException) as e:
1426 # fill in page with previously submitted values
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001427 for field in mandatory_fields:
1428 context.__setitem__(field, request.POST.get(field, "-- missing"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001429 if isinstance(e, IntegrityError) and "username" in str(e):
1430 context['alert'] = "Your chosen username is already used"
1431 else:
1432 context['alert'] = str(e)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001433 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001434
1435 raise Exception("Invalid HTTP method for this page")
1436
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001437 # Shows the edit project page
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001438 def project(request, pid):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001439 project = Project.objects.get(pk=pid)
1440 context = {"project": project}
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001441 return toaster_render(request, "project.html", context)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001442
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001443 def jsunittests(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001444 """ Provides a page for the js unit tests """
1445 bbv = BitbakeVersion.objects.filter(branch="master").first()
1446 release = Release.objects.filter(bitbake_version=bbv).first()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001447
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001448 name = "_js_unit_test_prj_"
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001449
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001450 # If there is an existing project by this name delete it.
1451 # We don't want Lots of duplicates cluttering up the projects.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001452 Project.objects.filter(name=name).delete()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001453
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001454 new_project = Project.objects.create_project(name=name,
1455 release=release)
1456 # Add a layer
1457 layer = new_project.get_all_compatible_layer_versions().first()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001458
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001459 ProjectLayer.objects.get_or_create(layercommit=layer,
1460 project=new_project)
1461
1462 # make sure we have a machine set for this project
1463 ProjectVariable.objects.get_or_create(project=new_project,
1464 name="MACHINE",
1465 value="qemux86")
1466 context = {'project': new_project}
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001467 return toaster_render(request, "js-unit-tests.html", context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001468
1469 from django.views.decorators.csrf import csrf_exempt
1470 @csrf_exempt
1471 def xhr_testreleasechange(request, pid):
1472 def response(data):
1473 return HttpResponse(jsonfilter(data),
1474 content_type="application/json")
1475
1476 """ returns layer versions that would be deleted on the new
1477 release__pk """
1478 try:
1479 prj = Project.objects.get(pk = pid)
1480 new_release_id = request.GET['new_release_id']
1481
1482 # If we're already on this project do nothing
1483 if prj.release.pk == int(new_release_id):
1484 return reponse({"error": "ok", "rows": []})
1485
1486 retval = []
1487
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001488 for project in prj.projectlayer_set.all():
1489 release = Release.objects.get(pk = new_release_id)
1490
1491 layer_versions = prj.get_all_compatible_layer_versions()
1492 layer_versions = layer_versions.filter(release = release)
1493 layer_versions = layer_versions.filter(layer__name = project.layercommit.layer.name)
1494
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001495 # there is no layer_version with the new release id,
1496 # and the same name
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001497 if layer_versions.count() < 1:
1498 retval.append(project)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001499
1500 return response({"error":"ok",
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001501 "rows": [_lv_to_dict(prj) for y in [x.layercommit for x in retval]]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001502 })
1503
1504 except Exception as e:
1505 return response({"error": str(e) })
1506
1507 def xhr_configvaredit(request, pid):
1508 try:
1509 prj = Project.objects.get(id = pid)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001510 # There are cases where user can add variables which hold values
1511 # like http://, file:/// etc. In such case a simple split(":")
1512 # would fail. One example is SSTATE_MIRRORS variable. So we use
1513 # max_split var to handle them.
1514 max_split = 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001515 # add conf variables
1516 if 'configvarAdd' in request.POST:
1517 t=request.POST['configvarAdd'].strip()
1518 if ":" in t:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001519 variable, value = t.split(":", max_split)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001520 else:
1521 variable = t
1522 value = ""
1523
1524 pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable, value = value)
1525 # change conf variables
1526 if 'configvarChange' in request.POST:
1527 t=request.POST['configvarChange'].strip()
1528 if ":" in t:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001529 variable, value = t.split(":", max_split)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001530 else:
1531 variable = t
1532 value = ""
1533
1534 pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable)
1535 pt.value=value
1536 pt.save()
1537 # remove conf variables
1538 if 'configvarDel' in request.POST:
1539 t=request.POST['configvarDel'].strip()
1540 pt = ProjectVariable.objects.get(pk = int(t)).delete()
1541
1542 # return all project settings, filter out blacklist and elsewhere-managed variables
1543 vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context()
1544 configvars_query = ProjectVariable.objects.filter(project_id = pid).all()
1545 for var in vars_managed:
1546 configvars_query = configvars_query.exclude(name = var)
1547 for var in vars_blacklist:
1548 configvars_query = configvars_query.exclude(name = var)
1549
1550 return_data = {
1551 "error": "ok",
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001552 'configvars': [(x.name, x.value, x.pk) for x in configvars_query]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001553 }
1554 try:
1555 return_data['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value,
1556 except ProjectVariable.DoesNotExist:
1557 pass
1558 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001559 return_data['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value,
1560 except ProjectVariable.DoesNotExist:
1561 pass
1562 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001563 return_data['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value,
1564 except ProjectVariable.DoesNotExist:
1565 pass
1566 try:
1567 return_data['image_install_append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL_append").value,
1568 except ProjectVariable.DoesNotExist:
1569 pass
1570 try:
1571 return_data['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value,
1572 except ProjectVariable.DoesNotExist:
1573 pass
1574 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001575 return_data['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001576 except ProjectVariable.DoesNotExist:
1577 pass
1578
1579 return HttpResponse(json.dumps( return_data ), content_type = "application/json")
1580
1581 except Exception as e:
1582 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1583
1584
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001585 def customrecipe_download(request, pid, recipe_id):
1586 recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id)
1587
1588 file_data = recipe.generate_recipe_file_contents()
1589
1590 response = HttpResponse(file_data, content_type='text/plain')
1591 response['Content-Disposition'] = \
1592 'attachment; filename="%s_%s.bb"' % (recipe.name,
1593 recipe.version)
1594
1595 return response
1596
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001597 def importlayer(request, pid):
1598 template = "importlayer.html"
1599 context = {
1600 'project': Project.objects.get(id=pid),
1601 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001602 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001603
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001604 def layerdetails(request, pid, layerid):
1605 project = Project.objects.get(pk=pid)
1606 layer_version = Layer_Version.objects.get(pk=layerid)
1607
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001608 project_layers = ProjectLayer.objects.filter(
1609 project=project).values_list("layercommit_id",
1610 flat=True)
1611
1612 context = {
1613 'project': project,
1614 'layer_source': LayerSource.types_dict(),
1615 'layerversion': layer_version,
1616 'layerdeps': {
1617 "list": [
1618 {
1619 "id": dep.id,
1620 "name": dep.layer.name,
1621 "layerdetailurl": reverse('layerdetails',
1622 args=(pid, dep.pk)),
1623 "vcs_url": dep.layer.vcs_url,
1624 "vcs_reference": dep.get_vcs_reference()
1625 }
1626 for dep in layer_version.get_alldeps(project.id)]
1627 },
1628 'projectlayers': list(project_layers)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001629 }
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001630
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001631 return toaster_render(request, 'layerdetails.html', context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001632
1633
1634 def get_project_configvars_context():
1635 # Vars managed outside of this view
1636 vars_managed = {
1637 'MACHINE', 'BBLAYERS'
1638 }
1639
1640 vars_blacklist = {
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001641 'PARALLEL_MAKE','BB_NUMBER_THREADS',
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001642 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001643 'PARALLEL_MAKE','TMPDIR',
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001644 'all_proxy','ftp_proxy','http_proxy ','https_proxy'
1645 }
1646
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001647 vars_fstypes = Target_Image_File.SUFFIXES
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001648
1649 return(vars_managed,sorted(vars_fstypes),vars_blacklist)
1650
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001651 def projectconf(request, pid):
1652
1653 try:
1654 prj = Project.objects.get(id = pid)
1655 except Project.DoesNotExist:
1656 return HttpResponseNotFound("<h1>Project id " + pid + " is unavailable</h1>")
1657
1658 # remove blacklist and externally managed varaibles from this list
1659 vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context()
1660 configvars = ProjectVariable.objects.filter(project_id = pid).all()
1661 for var in vars_managed:
1662 configvars = configvars.exclude(name = var)
1663 for var in vars_blacklist:
1664 configvars = configvars.exclude(name = var)
1665
1666 context = {
1667 'project': prj,
1668 'configvars': configvars,
1669 'vars_managed': vars_managed,
1670 'vars_fstypes': vars_fstypes,
1671 'vars_blacklist': vars_blacklist,
1672 }
1673
1674 try:
1675 context['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value
1676 context['distro_defined'] = "1"
1677 except ProjectVariable.DoesNotExist:
1678 pass
1679 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001680 if ProjectVariable.objects.get(project = prj, name = "DL_DIR").value == "${TOPDIR}/../downloads":
1681 be = BuildEnvironment.objects.get(pk = str(1))
1682 dl_dir = os.path.join(dirname(be.builddir), "downloads")
1683 context['dl_dir'] = dl_dir
1684 pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "DL_DIR")
1685 pv.value = dl_dir
1686 pv.save()
1687 else:
1688 context['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value
1689 context['dl_dir_defined'] = "1"
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001690 except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001691 pass
1692 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001693 context['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value
1694 context['fstypes_defined'] = "1"
1695 except ProjectVariable.DoesNotExist:
1696 pass
1697 try:
1698 context['image_install_append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL_append").value
1699 context['image_install_append_defined'] = "1"
1700 except ProjectVariable.DoesNotExist:
1701 pass
1702 try:
1703 context['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value
1704 context['package_classes_defined'] = "1"
1705 except ProjectVariable.DoesNotExist:
1706 pass
1707 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001708 if ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value == "${TOPDIR}/../sstate-cache":
1709 be = BuildEnvironment.objects.get(pk = str(1))
1710 sstate_dir = os.path.join(dirname(be.builddir), "sstate-cache")
1711 context['sstate_dir'] = sstate_dir
1712 pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "SSTATE_DIR")
1713 pv.value = sstate_dir
1714 pv.save()
1715 else:
1716 context['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value
1717 context['sstate_dir_defined'] = "1"
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001718 except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001719 pass
1720
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001721 return toaster_render(request, "projectconf.html", context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001722
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001723 def _file_names_for_artifact(build, artifact_type, artifact_id):
1724 """
1725 Return a tuple (file path, file name for the download response) for an
1726 artifact of type artifact_type with ID artifact_id for build; if
1727 artifact type is not supported, returns (None, None)
1728 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001729 file_name = None
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001730 response_file_name = None
1731
1732 if artifact_type == "cookerlog":
1733 file_name = build.cooker_log_path
1734 response_file_name = "cooker.log"
1735
1736 elif artifact_type == "imagefile":
1737 file_name = Target_Image_File.objects.get(target__build = build, pk = artifact_id).file_name
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001738
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001739 elif artifact_type == "targetkernelartifact":
1740 target = TargetKernelFile.objects.get(pk=artifact_id)
1741 file_name = target.file_name
1742
1743 elif artifact_type == "targetsdkartifact":
1744 target = TargetSDKFile.objects.get(pk=artifact_id)
1745 file_name = target.file_name
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001746
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001747 elif artifact_type == "licensemanifest":
1748 file_name = Target.objects.get(build = build, pk = artifact_id).license_manifest_path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001749
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001750 elif artifact_type == "packagemanifest":
1751 file_name = Target.objects.get(build = build, pk = artifact_id).package_manifest_path
1752
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001753 elif artifact_type == "tasklogfile":
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001754 file_name = Task.objects.get(build = build, pk = artifact_id).logfile
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001755
1756 elif artifact_type == "logmessagefile":
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001757 file_name = LogMessage.objects.get(build = build, pk = artifact_id).pathname
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001758
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001759 if file_name and not response_file_name:
1760 response_file_name = os.path.basename(file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001761
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001762 return (file_name, response_file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001763
1764 def build_artifact(request, build_id, artifact_type, artifact_id):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001765 """
1766 View which returns a build artifact file as a response
1767 """
1768 file_name = None
1769 response_file_name = None
1770
1771 try:
1772 build = Build.objects.get(pk = build_id)
1773 file_name, response_file_name = _file_names_for_artifact(
1774 build, artifact_type, artifact_id
1775 )
1776
1777 if file_name and response_file_name:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001778 fsock = open(file_name, "rb")
Patrick Williamsd7e96312015-09-22 08:09:05 -05001779 content_type = MimeTypeFinder.get_mimetype(file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001780
1781 response = HttpResponse(fsock, content_type = content_type)
1782
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001783 disposition = "attachment; filename=" + response_file_name
1784 response["Content-Disposition"] = disposition
Patrick Williamsd7e96312015-09-22 08:09:05 -05001785
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001786 return response
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001787 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001788 return toaster_render(request, "unavailable_artifact.html")
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001789 except (ObjectDoesNotExist, IOError):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001790 return toaster_render(request, "unavailable_artifact.html")
1791