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