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