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