# # BitBake Toaster Implementation # # Copyright (C) 2013 Intel Corporation # # SPDX-License-Identifier: GPL-2.0-only # import re from django.db.models import F, Q, Sum from django.db import IntegrityError from django.shortcuts import render, redirect, get_object_or_404 from django.utils.http import urlencode from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe from orm.models import LogMessage, Variable, Package_Dependency, Package from orm.models import Task_Dependency, Package_File from orm.models import Target_Installed_Package, Target_File from orm.models import TargetKernelFile, TargetSDKFile, Target_Image_File from orm.models import BitbakeVersion, CustomImageRecipe from django.urls import reverse, resolve from django.core.exceptions import ObjectDoesNotExist from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.http import HttpResponseNotFound, JsonResponse from django.utils import timezone from datetime import timedelta, datetime from toastergui.templatetags.projecttags import json as jsonfilter from decimal import Decimal import json import os from os.path import dirname import mimetypes import logging logger = logging.getLogger("toaster") # Project creation and managed build enable project_enable = ('1' == os.environ.get('TOASTER_BUILDSERVER')) is_project_specific = ('1' == os.environ.get('TOASTER_PROJECTSPECIFIC')) class MimeTypeFinder(object): # setting this to False enables additional non-standard mimetypes # to be included in the guess _strict = False # returns the mimetype for a file path as a string, # or 'application/octet-stream' if the type couldn't be guessed @classmethod def get_mimetype(self, path): guess = mimetypes.guess_type(path, self._strict) guessed_type = guess[0] if guessed_type is None: guessed_type = 'application/octet-stream' return guessed_type # single point to add global values into the context before rendering def toaster_render(request, page, context): context['project_enable'] = project_enable context['project_specific'] = is_project_specific return render(request, page, context) # all new sessions should come through the landing page; # determine in which mode we are running in, and redirect appropriately def landing(request): # in build mode, we redirect to the command-line builds page # if there are any builds for the default (cli builds) project default_project = Project.objects.get_or_create_default_project() default_project_builds = Build.objects.filter(project = default_project) # we only redirect to projects page if there is a user-generated project num_builds = Build.objects.all().count() user_projects = Project.objects.filter(is_default = False) has_user_project = user_projects.count() > 0 if num_builds == 0 and has_user_project: return redirect(reverse('all-projects'), permanent = False) if num_builds > 0: return redirect(reverse('all-builds'), permanent = False) context = {'lvs_nos' : Layer_Version.objects.all().count()} return toaster_render(request, 'landing.html', context) def objtojson(obj): from django.db.models.query import QuerySet from django.db.models import Model if isinstance(obj, datetime): return obj.isoformat() elif isinstance(obj, timedelta): return obj.total_seconds() elif isinstance(obj, QuerySet) or isinstance(obj, set): return list(obj) elif isinstance(obj, Decimal): return str(obj) elif type(obj).__name__ == "RelatedManager": return [x.pk for x in obj.all()] elif hasattr( obj, '__dict__') and isinstance(obj, Model): d = obj.__dict__ nd = dict(d) for di in d.keys(): if di.startswith("_"): del nd[di] elif isinstance(d[di], Model): nd[di] = d[di].pk elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di): nd[di] = getattr(obj, "get_%s_display" % di)() return nd elif isinstance( obj, type(lambda x:x)): import inspect return inspect.getsourcelines(obj)[0] else: raise TypeError("Unserializable object %s (%s) of type %s" % ( obj, dir(obj), type(obj))) def _lv_to_dict(prj, x = None): if x is None: def wrapper(x): return _lv_to_dict(prj, x) return wrapper return {"id": x.pk, "name": x.layer.name, "tooltip": "%s | %s" % (x.layer.vcs_url,x.get_vcs_reference()), "detail": "(%s" % x.layer.vcs_url + (")" if x.release is None else " | "+x.get_vcs_reference()+")"), "giturl": x.layer.vcs_url, "layerdetailurl" : reverse('layerdetails', args=(prj.id,x.pk)), "revision" : x.get_vcs_reference(), } def _build_page_range(paginator, index = 1): try: page = paginator.page(index) except PageNotAnInteger: page = paginator.page(1) except EmptyPage: page = paginator.page(paginator.num_pages) page.page_range = [page.number] crt_range = 0 for i in range(1,5): if (page.number + i) <= paginator.num_pages: page.page_range = page.page_range + [ page.number + i] crt_range +=1 if (page.number - i) > 0: page.page_range = [page.number -i] + page.page_range crt_range +=1 if crt_range == 4: break return page def _verify_parameters(g, mandatory_parameters): miss = [] for mp in mandatory_parameters: if not mp in g: miss.append(mp) if len(miss): return miss return None def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs): try: from urllib import unquote, urlencode except ImportError: from urllib.parse import unquote, urlencode url = reverse(view, kwargs=kwargs) params = {} for i in g: params[i] = g[i] for i in mandatory_parameters: if not i in params: params[i] = unquote(str(mandatory_parameters[i])) return redirect(url + "?%s" % urlencode(params), permanent = False, **kwargs) class RedirectException(Exception): def __init__(self, view, g, mandatory_parameters, *args, **kwargs): super(RedirectException, self).__init__() self.view = view self.g = g self.mandatory_parameters = mandatory_parameters self.oargs = args self.okwargs = kwargs def get_redirect_response(self): return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, **self.okwargs) FIELD_SEPARATOR = ":" AND_VALUE_SEPARATOR = "!" OR_VALUE_SEPARATOR = "|" DESCENDING = "-" def __get_q_for_val(name, value): if "OR" in value or "AND" in value: result = None for x in value.split("OR"): x = __get_q_for_val(name, x) result = result | x if result else x return result if "AND" in value: result = None for x in value.split("AND"): x = __get_q_for_val(name, x) result = result & x if result else x return result if value.startswith("NOT"): value = value[3:] if value == 'None': value = None kwargs = { name : value } return ~Q(**kwargs) else: if value == 'None': value = None kwargs = { name : value } return Q(**kwargs) def _get_filtering_query(filter_string): search_terms = filter_string.split(FIELD_SEPARATOR) and_keys = search_terms[0].split(AND_VALUE_SEPARATOR) and_values = search_terms[1].split(AND_VALUE_SEPARATOR) and_query = None for kv in zip(and_keys, and_values): or_keys = kv[0].split(OR_VALUE_SEPARATOR) or_values = kv[1].split(OR_VALUE_SEPARATOR) query = None for key, val in zip(or_keys, or_values): x = __get_q_for_val(key, val) query = query | x if query else x and_query = and_query & query if and_query else query return and_query def _get_toggle_order(request, orderkey, toggle_reverse = False): if toggle_reverse: return "%s:+" % orderkey if request.GET.get('orderby', "") == "%s:-" % orderkey else "%s:-" % orderkey else: return "%s:-" % orderkey if request.GET.get('orderby', "") == "%s:+" % orderkey else "%s:+" % orderkey def _get_toggle_order_icon(request, orderkey): if request.GET.get('orderby', "") == "%s:+"%orderkey: return "down" elif request.GET.get('orderby', "") == "%s:-"%orderkey: return "up" else: return None # we check that the input comes in a valid form that we can recognize def _validate_input(field_input, model): invalid = None if field_input: field_input_list = field_input.split(FIELD_SEPARATOR) # Check we have only one colon if len(field_input_list) != 2: invalid = "We have an invalid number of separators: " + field_input + " -> " + str(field_input_list) return None, invalid # Check we have an equal number of terms both sides of the colon if len(field_input_list[0].split(AND_VALUE_SEPARATOR)) != len(field_input_list[1].split(AND_VALUE_SEPARATOR)): invalid = "Not all arg names got values" return None, invalid + str(field_input_list) # Check we are looking for a valid field valid_fields = [f.name for f in model._meta.get_fields()] for field in field_input_list[0].split(AND_VALUE_SEPARATOR): if True in [field.startswith(x) for x in valid_fields]: break else: return None, (field, valid_fields) return field_input, invalid # uses search_allowed_fields in orm/models.py to create a search query # for these fields with the supplied input text def _get_search_results(search_term, queryset, model): search_object = None for st in search_term.split(" "): queries = None for field in model.search_allowed_fields: query = Q(**{field + '__icontains': st}) queries = queries | query if queries else query search_object = search_object & queries if search_object else queries queryset = queryset.filter(search_object) return queryset # function to extract the search/filter/ordering parameters from the request # it uses the request and the model to validate input for the filter and orderby values def _search_tuple(request, model): ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model) if invalid: raise BaseException("Invalid ordering model:" + str(model) + str(invalid)) filter_string, invalid = _validate_input(request.GET.get('filter', ''), model) if invalid: raise BaseException("Invalid filter " + str(invalid)) search_term = request.GET.get('search', '') return (filter_string, search_term, ordering_string) # returns a lazy-evaluated queryset for a filter/search/order combination def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''): if filter_string: filter_query = _get_filtering_query(filter_string) queryset = queryset.filter(filter_query) else: queryset = queryset.all() if search_term: queryset = _get_search_results(search_term, queryset, model) if ordering_string: column, order = ordering_string.split(':') if column == re.sub('-','',ordering_secondary): ordering_secondary='' if order.lower() == DESCENDING: column = '-' + column if ordering_secondary: queryset = queryset.order_by(column, ordering_secondary) else: queryset = queryset.order_by(column) # insure only distinct records (e.g. from multiple search hits) are returned return queryset.distinct() # returns the value of entries per page and the name of the applied sorting field. # if the value is given explicitly as a GET parameter it will be the first selected, # otherwise the cookie value will be used. def _get_parameters_values(request, default_count, default_order): current_url = resolve(request.path_info).url_name pagesize = request.GET.get('count', request.session.get('%s_count' % current_url, default_count)) orderby = request.GET.get('orderby', request.session.get('%s_orderby' % current_url, default_order)) return (pagesize, orderby) # set cookies for parameters. this is usefull in case parameters are set # manually from the GET values of the link def _set_parameters_values(pagesize, orderby, request): from django.urls import resolve current_url = resolve(request.path_info).url_name request.session['%s_count' % current_url] = pagesize request.session['%s_orderby' % current_url] =orderby # date range: normalize GUI's dd/mm/yyyy to date object def _normalize_input_date(date_str,default): date_str=re.sub('/', '-', date_str) # accept dd/mm/yyyy to d/m/yy try: date_in = datetime.strptime(date_str, "%d-%m-%Y") except ValueError: # courtesy try with two digit year try: date_in = datetime.strptime(date_str, "%d-%m-%y") except ValueError: return default date_in = date_in.replace(tzinfo=default.tzinfo) return date_in # convert and normalize any received date range filter, for example: # "completed_on__gte!completed_on__lt:01/03/2015!02/03/2015_daterange" to # "completed_on__gte!completed_on__lt:2015-03-01!2015-03-02" def _modify_date_range_filter(filter_string): # was the date range radio button selected? if 0 > filter_string.find('_daterange'): return filter_string,'' # normalize GUI dates to database format filter_string = filter_string.replace('_daterange','').replace(':','!'); filter_list = filter_string.split('!'); if 4 != len(filter_list): return filter_string today = timezone.localtime(timezone.now()) date_id = filter_list[1] date_from = _normalize_input_date(filter_list[2],today) date_to = _normalize_input_date(filter_list[3],today) # swap dates if manually set dates are out of order if date_to < date_from: date_to,date_from = date_from,date_to # convert to strings, make 'date_to' inclusive by moving to begining of next day date_from_str = date_from.strftime("%Y-%m-%d") date_to_str = (date_to+timedelta(days=1)).strftime("%Y-%m-%d") filter_string=filter_list[0]+'!'+filter_list[1]+':'+date_from_str+'!'+date_to_str daterange_selected = re.sub('__.*','', date_id) return filter_string,daterange_selected def _add_daterange_context(queryset_all, request, daterange_list): # calculate the exact begining of local today and yesterday today_begin = timezone.localtime(timezone.now()) yesterday_begin = today_begin - timedelta(days=1) # add daterange persistent context_date = {} context_date['last_date_from'] = request.GET.get('last_date_from',timezone.localtime(timezone.now()).strftime("%d/%m/%Y")) context_date['last_date_to' ] = request.GET.get('last_date_to' ,context_date['last_date_from']) # calculate the date ranges, avoid second sort for 'created' # fetch the respective max range from the database context_date['daterange_filter']='' for key in daterange_list: queryset_key = queryset_all.order_by(key) try: context_date['dateMin_'+key]=timezone.localtime(getattr(queryset_key.first(),key)).strftime("%d/%m/%Y") except AttributeError: context_date['dateMin_'+key]=timezone.localtime(timezone.now()) try: context_date['dateMax_'+key]=timezone.localtime(getattr(queryset_key.last(),key)).strftime("%d/%m/%Y") except AttributeError: context_date['dateMax_'+key]=timezone.localtime(timezone.now()) return context_date,today_begin,yesterday_begin ## # build dashboard for a single build, coming in as argument # Each build may contain multiple targets and each target # may generate multiple image files. display them all. # def builddashboard( request, build_id ): template = "builddashboard.html" if Build.objects.filter( pk=build_id ).count( ) == 0 : return redirect( builds ) build = Build.objects.get( pk = build_id ); layerVersionId = Layer_Version.objects.filter( build = build_id ); recipeCount = Recipe.objects.filter( layer_version__id__in = layerVersionId ).count( ); tgts = Target.objects.filter( build_id = build_id ).order_by( 'target' ); # set up custom target list with computed package and image data targets = [] ntargets = 0 # True if at least one target for this build has an SDK artifact # or image file has_artifacts = False for t in tgts: elem = {} elem['target'] = t target_has_images = False image_files = [] npkg = 0 pkgsz = 0 package = None # Chunk the query to avoid "too many SQL variables" error package_set = t.target_installed_package_set.all() package_set_len = len(package_set) for ps_start in range(0,package_set_len,500): ps_stop = min(ps_start+500,package_set_len) for package in Package.objects.filter(id__in = [x.package_id for x in package_set[ps_start:ps_stop]]): pkgsz = pkgsz + package.size if package.installed_name: npkg = npkg + 1 elem['npkg'] = npkg elem['pkgsz'] = pkgsz ti = Target_Image_File.objects.filter(target_id = t.id) for i in ti: ndx = i.file_name.rfind('/') if ndx < 0: ndx = 0; f = i.file_name[ndx + 1:] image_files.append({ 'id': i.id, 'path': f, 'size': i.file_size, 'suffix': i.suffix }) if len(image_files) > 0: target_has_images = True elem['targetHasImages'] = target_has_images elem['imageFiles'] = image_files elem['target_kernel_artifacts'] = t.targetkernelfile_set.all() target_sdk_files = t.targetsdkfile_set.all() target_sdk_artifacts_count = target_sdk_files.count() elem['target_sdk_artifacts_count'] = target_sdk_artifacts_count elem['target_sdk_artifacts'] = target_sdk_files if target_has_images or target_sdk_artifacts_count > 0: has_artifacts = True targets.append(elem) ## # how many packages in this build - ignore anonymous ones # packageCount = 0 packages = Package.objects.filter( build_id = build_id ) for p in packages: if ( p.installed_name ): packageCount = packageCount + 1 logmessages = list(LogMessage.objects.filter( build = build_id )) context = { 'build' : build, 'project' : build.project, 'hasArtifacts' : has_artifacts, 'ntargets' : ntargets, 'targets' : targets, 'recipecount' : recipeCount, 'packagecount' : packageCount, 'logmessages' : logmessages, } return toaster_render( request, template, context ) def generateCoveredList2( revlist = None ): if not revlist: revlist = [] covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ] while len(covered_list): revlist = [ x for x in revlist if x.outcome != Task.OUTCOME_COVERED ] if len(revlist) > 0: return revlist newlist = _find_task_revdep_list(covered_list) revlist = list(set(revlist + newlist)) covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ] return revlist def task( request, build_id, task_id ): template = "task.html" tasks_list = Task.objects.filter( pk=task_id ) if tasks_list.count( ) == 0: return redirect( builds ) task_object = tasks_list[ 0 ]; dependencies = sorted( _find_task_dep( task_object ), key=lambda t:'%s_%s %s'%(t.recipe.name, t.recipe.version, t.task_name)) reverse_dependencies = sorted( _find_task_revdep( task_object ), key=lambda t:'%s_%s %s'%( t.recipe.name, t.recipe.version, t.task_name )) coveredBy = ''; if ( task_object.outcome == Task.OUTCOME_COVERED ): # _list = generateCoveredList( task ) coveredBy = sorted(generateCoveredList2( _find_task_revdep( task_object ) ), key = lambda x: x.recipe.name) log_head = '' log_body = '' if task_object.outcome == task_object.OUTCOME_FAILED: pass uri_list= [ ] variables = Variable.objects.filter(build=build_id) v=variables.filter(variable_name='SSTATE_DIR') if v.count() > 0: uri_list.append(v[0].variable_value) v=variables.filter(variable_name='SSTATE_MIRRORS') if (v.count() > 0): for mirror in v[0].variable_value.split('\\n'): s=re.sub('.* ','',mirror.strip(' \t\n\r')) if len(s): uri_list.append(s) context = { 'build' : Build.objects.filter( pk = build_id )[ 0 ], 'object' : task_object, 'task' : task_object, 'covered_by' : coveredBy, 'deps' : dependencies, 'rdeps' : reverse_dependencies, 'log_head' : log_head, 'log_body' : log_body, 'showing_matches' : False, 'uri_list' : uri_list, 'task_in_tasks_table_pg': int(task_object.order / 25) + 1 } if request.GET.get( 'show_matches', "" ): context[ 'showing_matches' ] = True context[ 'matching_tasks' ] = Task.objects.filter( sstate_checksum=task_object.sstate_checksum ).filter( build__completed_on__lt=task_object.build.completed_on).exclude( order__isnull=True).exclude(outcome=Task.OUTCOME_NA).order_by('-build__completed_on') return toaster_render( request, template, context ) def recipe(request, build_id, recipe_id, active_tab="1"): template = "recipe.html" if Recipe.objects.filter(pk=recipe_id).count() == 0 : return redirect(builds) recipe_object = Recipe.objects.get(pk=recipe_id) layer_version = Layer_Version.objects.get(pk=recipe_object.layer_version_id) layer = Layer.objects.get(pk=layer_version.layer_id) 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) package_count = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0).count() if active_tab != '1' and active_tab != '3' and active_tab != '4' : active_tab = '1' tab_states = {'1': '', '3': '', '4': ''} tab_states[active_tab] = 'active' context = { 'build' : Build.objects.get(pk=build_id), 'object' : recipe_object, 'layer_version' : layer_version, 'layer' : layer, 'tasks' : tasks_list, 'package_count' : package_count, 'tab_states' : tab_states, } return toaster_render(request, template, context) def recipe_packages(request, build_id, recipe_id): template = "recipe_packages.html" if Recipe.objects.filter(pk=recipe_id).count() == 0 : return redirect(builds) (pagesize, orderby) = _get_parameters_values(request, 10, 'name:+') mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby } retval = _verify_parameters( request.GET, mandatory_parameters ) if retval: return _redirect_parameters( 'recipe_packages', request.GET, mandatory_parameters, build_id = build_id, recipe_id = recipe_id) (filter_string, search_term, ordering_string) = _search_tuple(request, Package) recipe_object = Recipe.objects.get(pk=recipe_id) queryset = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0) package_count = queryset.count() queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name') packages = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1)) context = { 'build' : Build.objects.get(pk=build_id), 'recipe' : recipe_object, 'objects' : packages, 'object_count' : package_count, 'tablecols':[ { 'name':'Package', 'orderfield': _get_toggle_order(request,"name"), 'ordericon': _get_toggle_order_icon(request,"name"), 'orderkey': "name", }, { 'name':'Version', }, { 'name':'Size', 'orderfield': _get_toggle_order(request,"size", True), 'ordericon': _get_toggle_order_icon(request,"size"), 'orderkey': 'size', 'dclass': 'sizecol span2', }, ] } response = toaster_render(request, template, context) _set_parameters_values(pagesize, orderby, request) return response from django.http import HttpResponse def xhr_dirinfo(request, build_id, target_id): top = request.GET.get('start', '/') return HttpResponse(_get_dir_entries(build_id, target_id, top), content_type = "application/json") from django.utils.functional import Promise from django.utils.encoding import force_text class LazyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, Promise): return force_text(obj) return super(LazyEncoder, self).default(obj) from toastergui.templatetags.projecttags import filtered_filesizeformat import os def _get_dir_entries(build_id, target_id, start): node_str = { Target_File.ITYPE_REGULAR : '-', Target_File.ITYPE_DIRECTORY : 'd', Target_File.ITYPE_SYMLINK : 'l', Target_File.ITYPE_SOCKET : 's', Target_File.ITYPE_FIFO : 'p', Target_File.ITYPE_CHARACTER : 'c', Target_File.ITYPE_BLOCK : 'b', } response = [] objects = Target_File.objects.filter(target__exact=target_id, directory__path=start) target_packages = Target_Installed_Package.objects.filter(target__exact=target_id).values_list('package_id', flat=True) for o in objects: # exclude root inode '/' if o.path == '/': continue try: entry = {} entry['parent'] = start entry['name'] = os.path.basename(o.path) entry['fullpath'] = o.path # set defaults, not all dentries have packages entry['installed_package'] = None entry['package_id'] = None entry['package'] = None entry['link_to'] = None if o.inodetype == Target_File.ITYPE_DIRECTORY: entry['isdir'] = 1 # is there content in directory entry['childcount'] = Target_File.objects.filter(target__exact=target_id, directory__path=o.path).all().count() else: entry['isdir'] = 0 # resolve the file to get the package from the resolved file resolved_id = o.sym_target_id resolved_path = o.path if target_packages.count(): while resolved_id != "" and resolved_id is not None: tf = Target_File.objects.get(pk=resolved_id) resolved_path = tf.path resolved_id = tf.sym_target_id thisfile=Package_File.objects.all().filter(path__exact=resolved_path, package_id__in=target_packages) if thisfile.count(): p = Package.objects.get(pk=thisfile[0].package_id) entry['installed_package'] = p.installed_name entry['package_id'] = str(p.id) entry['package'] = p.name # don't use resolved path from above, show immediate link-to if o.sym_target_id != "" and o.sym_target_id is not None: entry['link_to'] = Target_File.objects.get(pk=o.sym_target_id).path entry['size'] = filtered_filesizeformat(o.size) if entry['link_to'] is not None: entry['permission'] = node_str[o.inodetype] + o.permission else: entry['permission'] = node_str[o.inodetype] + o.permission entry['owner'] = o.owner entry['group'] = o.group response.append(entry) except Exception as e: print("Exception ", e) traceback.print_exc() # sort by directories first, then by name rsorted = sorted(response, key=lambda entry : entry['name']) rsorted = sorted(rsorted, key=lambda entry : entry['isdir'], reverse=True) return json.dumps(rsorted, cls=LazyEncoder).replace(' 0: file_filter += 'conf/(local|bblayers).conf' if filter_string.find('conf/machine/') > 0: file_filter += 'conf/machine/' if filter_string.find('conf/distro/') > 0: file_filter += 'conf/distro/' if filter_string.find('/bitbake.conf') > 0: file_filter += '/bitbake.conf' build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path) build = Build.objects.get(pk=build_id) context = { 'objectname': 'configvars', 'object_search_display':'BitBake variables', 'filter_search_display':'variables', 'file_filter': file_filter, 'build': build, 'project': build.project, 'objects' : variables, 'total_count':queryset_with_search.count(), 'default_orderby' : 'variable_name:+', 'search_term':search_term, # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns 'tablecols' : [ {'name': 'Variable', '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", 'orderfield': _get_toggle_order(request, "variable_name"), 'ordericon':_get_toggle_order_icon(request, "variable_name"), }, {'name': 'Value', 'qhelp': "The value assigned to the variable", }, {'name': 'Set in file', 'qhelp': "The last configuration file that touched the variable value", 'clclass': 'file', 'hidden' : 0, 'orderkey' : 'vhistory__file_name', 'filter' : { 'class' : 'vhistory__file_name', 'label': 'Show:', 'options' : [ ('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 local.conf and bblayers.conf configuration files inside the /build/conf/ directory'), ('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 /conf/machine/ directory'), ('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 /conf/distro/ directory'), ('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 layer.conf configuration file inside your layers'), ('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 bitbake.conf configuration file'), ] }, }, {'name': 'Description', 'qhelp': "A brief explanation of the variable", 'clclass': 'description', 'hidden' : 0, 'dclass': "span4", 'filter' : { 'class' : 'description', 'label': 'Show:', 'options' : [ ('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 meta/conf/documentation.conf'), ] }, }, ], } response = toaster_render(request, template, context) _set_parameters_values(pagesize, orderby, request) return response def bfile(request, build_id, package_id): template = 'bfile.html' files = Package_File.objects.filter(package = package_id) build = Build.objects.get(pk=build_id) context = { 'build': build, 'project': build.project, 'objects' : files } return toaster_render(request, template, context) # A set of dependency types valid for both included and built package views OTHER_DEPENDS_BASE = [ Package_Dependency.TYPE_RSUGGESTS, Package_Dependency.TYPE_RPROVIDES, Package_Dependency.TYPE_RREPLACES, Package_Dependency.TYPE_RCONFLICTS, ] # value for invalid row id INVALID_KEY = -1 """ Given a package id, target_id retrieves two sets of this image and package's dependencies. The return value is a dictionary consisting of two other lists: a list of 'runtime' dependencies, that is, having RDEPENDS values in source package's recipe, and a list of other dependencies, that is the list of possible recipe variables as found in OTHER_DEPENDS_BASE plus the RRECOMMENDS or TRECOMMENDS value. The lists are built in the sort order specified for the package runtime dependency views. """ def _get_package_dependencies(package_id, target_id = INVALID_KEY): runtime_deps = [] other_deps = [] other_depends_types = OTHER_DEPENDS_BASE if target_id != INVALID_KEY : rdepends_type = Package_Dependency.TYPE_TRDEPENDS other_depends_types += [Package_Dependency.TYPE_TRECOMMENDS] else : rdepends_type = Package_Dependency.TYPE_RDEPENDS other_depends_types += [Package_Dependency.TYPE_RRECOMMENDS] package = Package.objects.get(pk=package_id) if target_id != INVALID_KEY : alldeps = package.package_dependencies_source.filter(target_id__exact = target_id) else : alldeps = package.package_dependencies_source.all() for idep in alldeps: dep_package = Package.objects.get(pk=idep.depends_on_id) dep_entry = Package_Dependency.DEPENDS_DICT[idep.dep_type] if dep_package.version == '' : version = '' else : version = dep_package.version + "-" + dep_package.revision installed = False if target_id != INVALID_KEY : if Target_Installed_Package.objects.filter(target_id__exact = target_id, package_id__exact = dep_package.id).count() > 0: installed = True dep = { 'name' : dep_package.name, 'version' : version, 'size' : dep_package.size, 'dep_type' : idep.dep_type, 'dep_type_display' : dep_entry[0].capitalize(), 'dep_type_help' : dep_entry[1] % (dep_package.name, package.name), 'depends_on_id' : dep_package.id, 'installed' : installed, } if target_id != INVALID_KEY: dep['alias'] = _get_package_alias(dep_package) if idep.dep_type == rdepends_type : runtime_deps.append(dep) elif idep.dep_type in other_depends_types : other_deps.append(dep) rdep_sorted = sorted(runtime_deps, key=lambda k: k['name']) odep_sorted = sorted( sorted(other_deps, key=lambda k: k['name']), key=lambda k: k['dep_type']) retvalues = {'runtime_deps' : rdep_sorted, 'other_deps' : odep_sorted} return retvalues # Return the count of packages dependent on package for this target_id image def _get_package_reverse_dep_count(package, target_id): return package.package_dependencies_target.filter(target_id__exact=target_id, dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count() # Return the count of the packages that this package_id is dependent on. # Use one of the two RDEPENDS types, either TRDEPENDS if the package was # installed, or else RDEPENDS if only built. def _get_package_dependency_count(package, target_id, is_installed): if is_installed : return package.package_dependencies_source.filter(target_id__exact = target_id, dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count() else : return package.package_dependencies_source.filter(dep_type__exact = Package_Dependency.TYPE_RDEPENDS).count() def _get_package_alias(package): alias = package.installed_name if alias is not None and alias != '' and alias != package.name: return alias else: return '' def _get_fullpackagespec(package): r = package.name version_good = package.version is not None and package.version != '' revision_good = package.revision is not None and package.revision != '' if version_good or revision_good: r += '_' if version_good: r += package.version if revision_good: r += '-' if revision_good: r += package.revision return r def package_built_detail(request, build_id, package_id): template = "package_built_detail.html" if Build.objects.filter(pk=build_id).count() == 0 : return redirect(builds) # follow convention for pagination w/ search although not used for this view queryset = Package_File.objects.filter(package_id__exact=package_id) (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+') mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } retval = _verify_parameters( request.GET, mandatory_parameters ) if retval: return _redirect_parameters( 'package_built_detail', request.GET, mandatory_parameters, build_id = build_id, package_id = package_id) (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File) paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path') package = Package.objects.get(pk=package_id) package.fullpackagespec = _get_fullpackagespec(package) context = { 'build' : Build.objects.get(pk=build_id), 'package' : package, 'dependency_count' : _get_package_dependency_count(package, -1, False), 'objects' : paths, 'tablecols':[ { 'name':'File', 'orderfield': _get_toggle_order(request, "path"), 'ordericon':_get_toggle_order_icon(request, "path"), }, { 'name':'Size', 'orderfield': _get_toggle_order(request, "size", True), 'ordericon':_get_toggle_order_icon(request, "size"), 'dclass': 'sizecol span2', }, ] } if paths.all().count() < 2: context['disable_sort'] = True; response = toaster_render(request, template, context) _set_parameters_values(pagesize, orderby, request) return response def package_built_dependencies(request, build_id, package_id): template = "package_built_dependencies.html" if Build.objects.filter(pk=build_id).count() == 0 : return redirect(builds) package = Package.objects.get(pk=package_id) package.fullpackagespec = _get_fullpackagespec(package) dependencies = _get_package_dependencies(package_id) context = { 'build' : Build.objects.get(pk=build_id), 'package' : package, 'runtime_deps' : dependencies['runtime_deps'], 'other_deps' : dependencies['other_deps'], 'dependency_count' : _get_package_dependency_count(package, -1, False) } return toaster_render(request, template, context) def package_included_detail(request, build_id, target_id, package_id): template = "package_included_detail.html" if Build.objects.filter(pk=build_id).count() == 0 : return redirect(builds) # follow convention for pagination w/ search although not used for this view (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+') mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } retval = _verify_parameters( request.GET, mandatory_parameters ) if retval: return _redirect_parameters( 'package_included_detail', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id) (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File) queryset = Package_File.objects.filter(package_id__exact=package_id) paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path') package = Package.objects.get(pk=package_id) package.fullpackagespec = _get_fullpackagespec(package) package.alias = _get_package_alias(package) target = Target.objects.get(pk=target_id) context = { 'build' : Build.objects.get(pk=build_id), 'target' : target, 'package' : package, 'reverse_count' : _get_package_reverse_dep_count(package, target_id), 'dependency_count' : _get_package_dependency_count(package, target_id, True), 'objects': paths, 'tablecols':[ { 'name':'File', 'orderfield': _get_toggle_order(request, "path"), 'ordericon':_get_toggle_order_icon(request, "path"), }, { 'name':'Size', 'orderfield': _get_toggle_order(request, "size", True), 'ordericon':_get_toggle_order_icon(request, "size"), 'dclass': 'sizecol span2', }, ] } if paths.all().count() < 2: context['disable_sort'] = True response = toaster_render(request, template, context) _set_parameters_values(pagesize, orderby, request) return response def package_included_dependencies(request, build_id, target_id, package_id): template = "package_included_dependencies.html" if Build.objects.filter(pk=build_id).count() == 0 : return redirect(builds) package = Package.objects.get(pk=package_id) package.fullpackagespec = _get_fullpackagespec(package) package.alias = _get_package_alias(package) target = Target.objects.get(pk=target_id) dependencies = _get_package_dependencies(package_id, target_id) context = { 'build' : Build.objects.get(pk=build_id), 'package' : package, 'target' : target, 'runtime_deps' : dependencies['runtime_deps'], 'other_deps' : dependencies['other_deps'], 'reverse_count' : _get_package_reverse_dep_count(package, target_id), 'dependency_count' : _get_package_dependency_count(package, target_id, True) } return toaster_render(request, template, context) def package_included_reverse_dependencies(request, build_id, target_id, package_id): template = "package_included_reverse_dependencies.html" if Build.objects.filter(pk=build_id).count() == 0 : return redirect(builds) (pagesize, orderby) = _get_parameters_values(request, 25, 'package__name:+') mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby } retval = _verify_parameters( request.GET, mandatory_parameters ) if retval: return _redirect_parameters( 'package_included_reverse_dependencies', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id) (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File) queryset = Package_Dependency.objects.select_related('depends_on').filter(depends_on=package_id, target_id=target_id, dep_type=Package_Dependency.TYPE_TRDEPENDS) objects = _get_queryset(Package_Dependency, queryset, filter_string, search_term, ordering_string, 'package__name') package = Package.objects.get(pk=package_id) package.fullpackagespec = _get_fullpackagespec(package) package.alias = _get_package_alias(package) target = Target.objects.get(pk=target_id) for o in objects: if o.package.version != '': o.package.version += '-' + o.package.revision o.alias = _get_package_alias(o.package) context = { 'build' : Build.objects.get(pk=build_id), 'package' : package, 'target' : target, 'objects' : objects, 'reverse_count' : _get_package_reverse_dep_count(package, target_id), 'dependency_count' : _get_package_dependency_count(package, target_id, True), 'tablecols':[ { 'name':'Package', 'orderfield': _get_toggle_order(request, "package__name"), 'ordericon': _get_toggle_order_icon(request, "package__name"), }, { 'name':'Version', }, { 'name':'Size', 'orderfield': _get_toggle_order(request, "package__size", True), 'ordericon': _get_toggle_order_icon(request, "package__size"), 'dclass': 'sizecol span2', }, ] } if objects.all().count() < 2: context['disable_sort'] = True response = toaster_render(request, template, context) _set_parameters_values(pagesize, orderby, request) return response def image_information_dir(request, build_id, target_id, packagefile_id): # stubbed for now return redirect(builds) # the context processor that supplies data used across all the pages # a context processor which runs on every request; this provides the # projects and non_cli_projects (i.e. projects created by the user) # variables referred to in templates, which used to determine the # visibility of UI elements like the "New build" button def managedcontextprocessor(request): projects = Project.objects.all() ret = { "projects": projects, "non_cli_projects": projects.exclude(is_default=True), "DEBUG" : toastermain.settings.DEBUG, "TOASTER_BRANCH": toastermain.settings.TOASTER_BRANCH, "TOASTER_REVISION" : toastermain.settings.TOASTER_REVISION, } return ret # REST-based API calls to return build/building status to external Toaster # managers and aggregators via JSON def _json_build_status(build_id,extend): build_stat = None try: build = Build.objects.get( pk = build_id ) build_stat = {} build_stat['id'] = build.id build_stat['name'] = build.build_name build_stat['machine'] = build.machine build_stat['distro'] = build.distro build_stat['start'] = build.started_on # look up target name target= Target.objects.get( build = build ) if target: if target.task: build_stat['target'] = '%s:%s' % (target.target,target.task) else: build_stat['target'] = '%s' % (target.target) else: build_stat['target'] = '' # look up project name project = Project.objects.get( build = build ) if project: build_stat['project'] = project.name else: build_stat['project'] = '' if Build.IN_PROGRESS == build.outcome: now = timezone.now() timediff = now - build.started_on build_stat['seconds']='%.3f' % timediff.total_seconds() build_stat['clone']='%d:%d' % (build.repos_cloned,build.repos_to_clone) build_stat['parse']='%d:%d' % (build.recipes_parsed,build.recipes_to_parse) tf = Task.objects.filter(build = build) tfc = tf.count() if tfc > 0: tfd = tf.exclude(order__isnull=True).count() else: tfd = 0 build_stat['task']='%d:%d' % (tfd,tfc) else: build_stat['outcome'] = build.get_outcome_text() timediff = build.completed_on - build.started_on build_stat['seconds']='%.3f' % timediff.total_seconds() build_stat['stop'] = build.completed_on messages = LogMessage.objects.all().filter(build = build) errors = len(messages.filter(level=LogMessage.ERROR) | messages.filter(level=LogMessage.EXCEPTION) | messages.filter(level=LogMessage.CRITICAL)) build_stat['errors'] = errors warnings = len(messages.filter(level=LogMessage.WARNING)) build_stat['warnings'] = warnings if extend: build_stat['cooker_log'] = build.cooker_log_path except Exception as e: build_state = str(e) return build_stat def json_builds(request): build_table = [] builds = [] try: builds = Build.objects.exclude(outcome=Build.IN_PROGRESS).order_by("-started_on") for build in builds: build_table.append(_json_build_status(build.id,False)) except Exception as e: build_table = str(e) return JsonResponse({'builds' : build_table, 'count' : len(builds)}) def json_building(request): build_table = [] builds = [] try: builds = Build.objects.filter(outcome=Build.IN_PROGRESS).order_by("-started_on") for build in builds: build_table.append(_json_build_status(build.id,False)) except Exception as e: build_table = str(e) return JsonResponse({'building' : build_table, 'count' : len(builds)}) def json_build(request,build_id): return JsonResponse({'build' : _json_build_status(build_id,True)}) import toastermain.settings from orm.models import Project, ProjectLayer, ProjectVariable from bldcontrol.models import BuildEnvironment # we have a set of functions if we're in managed mode, or # a default "page not available" simple functions for interactive mode if True: from django.contrib.auth.models import User from django.contrib.auth import authenticate, login from orm.models import LayerSource, ToasterSetting, Release import traceback class BadParameterException(Exception): ''' The exception raised on invalid POST requests ''' pass # new project def newproject(request): if not project_enable: return redirect( landing ) template = "newproject.html" context = { 'email': request.user.email if request.user.is_authenticated else '', 'username': request.user.username if request.user.is_authenticated else '', 'releases': Release.objects.order_by("description"), } try: context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value except ToasterSetting.DoesNotExist: pass if request.method == "GET": # render new project page return toaster_render(request, template, context) elif request.method == "POST": mandatory_fields = ['projectname', 'ptype'] try: ptype = request.POST.get('ptype') if ptype == "import": mandatory_fields.append('importdir') else: mandatory_fields.append('projectversion') # make sure we have values for all mandatory_fields missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0] if missing: # set alert for missing fields raise BadParameterException("Fields missing: %s" % ", ".join(missing)) if not request.user.is_authenticated: user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass') if user is None: user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass") user = authenticate(username = user.username, password = 'nopass') login(request, user) # save the project if ptype == "import": if not os.path.isdir('%s/conf' % request.POST['importdir']): raise BadParameterException("Bad path or missing 'conf' directory (%s)" % request.POST['importdir']) from django.core import management management.call_command('buildimport', '--command=import', '--name=%s' % request.POST['projectname'], '--path=%s' % request.POST['importdir'], interactive=False) prj = Project.objects.get(name = request.POST['projectname']) prj.merged_attr = True prj.save() else: release = Release.objects.get(pk = request.POST.get('projectversion', None )) prj = Project.objects.create_project(name = request.POST['projectname'], release = release) prj.user_id = request.user.pk if 'mergeattr' == request.POST.get('mergeattr', ''): prj.merged_attr = True prj.save() return redirect(reverse(project, args=(prj.pk,)) + "?notify=new-project") except (IntegrityError, BadParameterException) as e: # fill in page with previously submitted values for field in mandatory_fields: context.__setitem__(field, request.POST.get(field, "-- missing")) if isinstance(e, IntegrityError) and "username" in str(e): context['alert'] = "Your chosen username is already used" else: context['alert'] = str(e) return toaster_render(request, template, context) raise Exception("Invalid HTTP method for this page") # new project def newproject_specific(request, pid): if not project_enable: return redirect( landing ) project = Project.objects.get(pk=pid) template = "newproject_specific.html" context = { 'email': request.user.email if request.user.is_authenticated else '', 'username': request.user.username if request.user.is_authenticated else '', 'releases': Release.objects.order_by("description"), 'projectname': project.name, 'project_pk': project.pk, } # WORKAROUND: if we already know release, redirect 'newproject_specific' to 'project_specific' if '1' == project.get_variable('INTERNAL_PROJECT_SPECIFIC_SKIPRELEASE'): return redirect(reverse(project_specific, args=(project.pk,))) try: context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value except ToasterSetting.DoesNotExist: pass if request.method == "GET": # render new project page return toaster_render(request, template, context) elif request.method == "POST": mandatory_fields = ['projectname', 'ptype'] try: ptype = request.POST.get('ptype') if ptype == "build": mandatory_fields.append('projectversion') # make sure we have values for all mandatory_fields missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0] if missing: # set alert for missing fields raise BadParameterException("Fields missing: %s" % ", ".join(missing)) if not request.user.is_authenticated: user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass') if user is None: user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass") user = authenticate(username = user.username, password = 'nopass') login(request, user) # save the project if ptype == "analysis": release = None else: release = Release.objects.get(pk = request.POST.get('projectversion', None )) prj = Project.objects.create_project(name = request.POST['projectname'], release = release, existing_project = project) prj.user_id = request.user.pk prj.save() return redirect(reverse(project_specific, args=(prj.pk,)) + "?notify=new-project") except (IntegrityError, BadParameterException) as e: # fill in page with previously submitted values for field in mandatory_fields: context.__setitem__(field, request.POST.get(field, "-- missing")) if isinstance(e, IntegrityError) and "username" in str(e): context['alert'] = "Your chosen username is already used" else: context['alert'] = str(e) return toaster_render(request, template, context) raise Exception("Invalid HTTP method for this page") # Shows the edit project page def project(request, pid): project = Project.objects.get(pk=pid) if '1' == os.environ.get('TOASTER_PROJECTSPECIFIC'): if request.GET: #Example:request.GET= params = urlencode(request.GET).replace('%5B%27','').replace('%27%5D','') return redirect("%s?%s" % (reverse(project_specific, args=(project.pk,)),params)) else: return redirect(reverse(project_specific, args=(project.pk,))) context = {"project": project} return toaster_render(request, "project.html", context) # Shows the edit project-specific page def project_specific(request, pid): project = Project.objects.get(pk=pid) # Are we refreshing from a successful project specific update clone? if Project.PROJECT_SPECIFIC_CLONING_SUCCESS == project.get_variable(Project.PROJECT_SPECIFIC_STATUS): return redirect(reverse(landing_specific,args=(project.pk,))) context = { "project": project, "is_new" : project.get_variable(Project.PROJECT_SPECIFIC_ISNEW), "default_image_recipe" : project.get_variable(Project.PROJECT_SPECIFIC_DEFAULTIMAGE), "mru" : Build.objects.all().filter(project=project,outcome=Build.IN_PROGRESS), } if project.build_set.filter(outcome=Build.IN_PROGRESS).count() > 0: context['build_in_progress_none_completed'] = True else: context['build_in_progress_none_completed'] = False return toaster_render(request, "project.html", context) # perform the final actions for the project specific page def project_specific_finalize(cmnd, pid): project = Project.objects.get(pk=pid) callback = project.get_variable(Project.PROJECT_SPECIFIC_CALLBACK) if "update" == cmnd: # Delete all '_PROJECT_PREPARE_' builds for b in Build.objects.all().filter(project=project): delete_build = False for t in b.target_set.all(): if '_PROJECT_PREPARE_' == t.target: delete_build = True if delete_build: from django.core import management management.call_command('builddelete', str(b.id), interactive=False) # perform callback at this last moment if defined, in case Toaster gets shutdown next default_target = project.get_variable(Project.PROJECT_SPECIFIC_DEFAULTIMAGE) if callback: callback = callback.replace("",default_target) if "cancel" == cmnd: if callback: callback = callback.replace("","none") callback = callback.replace("--update","--cancel") # perform callback at this last moment if defined, in case this Toaster gets shutdown next ret = '' if callback: ret = os.system('bash -c "%s"' % callback) project.set_variable(Project.PROJECT_SPECIFIC_CALLBACK,'') # Delete the temp project specific variables project.set_variable(Project.PROJECT_SPECIFIC_ISNEW,'') project.set_variable(Project.PROJECT_SPECIFIC_STATUS,Project.PROJECT_SPECIFIC_NONE) # WORKAROUND: Release this workaround flag project.set_variable('INTERNAL_PROJECT_SPECIFIC_SKIPRELEASE','') # Shows the final landing page for project specific update def landing_specific(request, pid): project_specific_finalize("update", pid) context = { "install_dir": os.environ['TOASTER_DIR'], } return toaster_render(request, "landing_specific.html", context) # Shows the related landing-specific page def landing_specific_cancel(request, pid): project_specific_finalize("cancel", pid) context = { "install_dir": os.environ['TOASTER_DIR'], "status": "cancel", } return toaster_render(request, "landing_specific.html", context) def jsunittests(request): """ Provides a page for the js unit tests """ bbv = BitbakeVersion.objects.filter(branch="master").first() release = Release.objects.filter(bitbake_version=bbv).first() name = "_js_unit_test_prj_" # If there is an existing project by this name delete it. # We don't want Lots of duplicates cluttering up the projects. Project.objects.filter(name=name).delete() new_project = Project.objects.create_project(name=name, release=release) # Add a layer layer = new_project.get_all_compatible_layer_versions().first() ProjectLayer.objects.get_or_create(layercommit=layer, project=new_project) # make sure we have a machine set for this project ProjectVariable.objects.get_or_create(project=new_project, name="MACHINE", value="qemux86") context = {'project': new_project} return toaster_render(request, "js-unit-tests.html", context) from django.views.decorators.csrf import csrf_exempt @csrf_exempt def xhr_testreleasechange(request, pid): def response(data): return HttpResponse(jsonfilter(data), content_type="application/json") """ returns layer versions that would be deleted on the new release__pk """ try: prj = Project.objects.get(pk = pid) new_release_id = request.GET['new_release_id'] # If we're already on this project do nothing if prj.release.pk == int(new_release_id): return reponse({"error": "ok", "rows": []}) retval = [] for project in prj.projectlayer_set.all(): release = Release.objects.get(pk = new_release_id) layer_versions = prj.get_all_compatible_layer_versions() layer_versions = layer_versions.filter(release = release) layer_versions = layer_versions.filter(layer__name = project.layercommit.layer.name) # there is no layer_version with the new release id, # and the same name if layer_versions.count() < 1: retval.append(project) return response({"error":"ok", "rows": [_lv_to_dict(prj) for y in [x.layercommit for x in retval]] }) except Exception as e: return response({"error": str(e) }) def xhr_configvaredit(request, pid): try: prj = Project.objects.get(id = pid) # There are cases where user can add variables which hold values # like http://, file:/// etc. In such case a simple split(":") # would fail. One example is SSTATE_MIRRORS variable. So we use # max_split var to handle them. max_split = 1 # add conf variables if 'configvarAdd' in request.POST: t=request.POST['configvarAdd'].strip() if ":" in t: variable, value = t.split(":", max_split) else: variable = t value = "" pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable, value = value) # change conf variables if 'configvarChange' in request.POST: t=request.POST['configvarChange'].strip() if ":" in t: variable, value = t.split(":", max_split) else: variable = t value = "" pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable) pt.value=value pt.save() # remove conf variables if 'configvarDel' in request.POST: t=request.POST['configvarDel'].strip() pt = ProjectVariable.objects.get(pk = int(t)).delete() # return all project settings, filter out blacklist and elsewhere-managed variables vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context() configvars_query = ProjectVariable.objects.filter(project_id = pid).all() for var in vars_managed: configvars_query = configvars_query.exclude(name = var) for var in vars_blacklist: configvars_query = configvars_query.exclude(name = var) return_data = { "error": "ok", 'configvars': [(x.name, x.value, x.pk) for x in configvars_query] } try: return_data['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value, except ProjectVariable.DoesNotExist: pass try: return_data['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value, except ProjectVariable.DoesNotExist: pass try: return_data['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value, except ProjectVariable.DoesNotExist: pass try: return_data['image_install:append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL:append").value, except ProjectVariable.DoesNotExist: pass try: return_data['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value, except ProjectVariable.DoesNotExist: pass try: return_data['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value, except ProjectVariable.DoesNotExist: pass return HttpResponse(json.dumps( return_data ), content_type = "application/json") except Exception as e: return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") def customrecipe_download(request, pid, recipe_id): recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id) file_data = recipe.generate_recipe_file_contents() response = HttpResponse(file_data, content_type='text/plain') response['Content-Disposition'] = \ 'attachment; filename="%s_%s.bb"' % (recipe.name, recipe.version) return response def importlayer(request, pid): template = "importlayer.html" context = { 'project': Project.objects.get(id=pid), } return toaster_render(request, template, context) def layerdetails(request, pid, layerid): project = Project.objects.get(pk=pid) layer_version = Layer_Version.objects.get(pk=layerid) project_layers = ProjectLayer.objects.filter( project=project).values_list("layercommit_id", flat=True) context = { 'project': project, 'layer_source': LayerSource.types_dict(), 'layerversion': layer_version, 'layerdeps': { "list": [ { "id": dep.id, "name": dep.layer.name, "layerdetailurl": reverse('layerdetails', args=(pid, dep.pk)), "vcs_url": dep.layer.vcs_url, "vcs_reference": dep.get_vcs_reference() } for dep in layer_version.get_alldeps(project.id)] }, 'projectlayers': list(project_layers) } return toaster_render(request, 'layerdetails.html', context) def get_project_configvars_context(): # Vars managed outside of this view vars_managed = { 'MACHINE', 'BBLAYERS' } vars_blacklist = { 'PARALLEL_MAKE','BB_NUMBER_THREADS', 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT', 'PARALLEL_MAKE','TMPDIR', 'all_proxy','ftp_proxy','http_proxy ','https_proxy' } vars_fstypes = Target_Image_File.SUFFIXES return(vars_managed,sorted(vars_fstypes),vars_blacklist) def projectconf(request, pid): try: prj = Project.objects.get(id = pid) except Project.DoesNotExist: return HttpResponseNotFound("

Project id " + pid + " is unavailable

") # remove blacklist and externally managed varaibles from this list vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context() configvars = ProjectVariable.objects.filter(project_id = pid).all() for var in vars_managed: configvars = configvars.exclude(name = var) for var in vars_blacklist: configvars = configvars.exclude(name = var) context = { 'project': prj, 'configvars': configvars, 'vars_managed': vars_managed, 'vars_fstypes': vars_fstypes, 'vars_blacklist': vars_blacklist, } try: context['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value context['distro_defined'] = "1" except ProjectVariable.DoesNotExist: pass try: if ProjectVariable.objects.get(project = prj, name = "DL_DIR").value == "${TOPDIR}/../downloads": be = BuildEnvironment.objects.get(pk = str(1)) dl_dir = os.path.join(dirname(be.builddir), "downloads") context['dl_dir'] = dl_dir pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "DL_DIR") pv.value = dl_dir pv.save() else: context['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value context['dl_dir_defined'] = "1" except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist): pass try: context['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value context['fstypes_defined'] = "1" except ProjectVariable.DoesNotExist: pass try: context['image_install:append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL:append").value context['image_install:append_defined'] = "1" except ProjectVariable.DoesNotExist: pass try: context['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value context['package_classes_defined'] = "1" except ProjectVariable.DoesNotExist: pass try: if ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value == "${TOPDIR}/../sstate-cache": be = BuildEnvironment.objects.get(pk = str(1)) sstate_dir = os.path.join(dirname(be.builddir), "sstate-cache") context['sstate_dir'] = sstate_dir pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "SSTATE_DIR") pv.value = sstate_dir pv.save() else: context['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value context['sstate_dir_defined'] = "1" except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist): pass return toaster_render(request, "projectconf.html", context) def _file_names_for_artifact(build, artifact_type, artifact_id): """ Return a tuple (file path, file name for the download response) for an artifact of type artifact_type with ID artifact_id for build; if artifact type is not supported, returns (None, None) """ file_name = None response_file_name = None if artifact_type == "cookerlog": file_name = build.cooker_log_path response_file_name = "cooker.log" elif artifact_type == "imagefile": file_name = Target_Image_File.objects.get(target__build = build, pk = artifact_id).file_name elif artifact_type == "targetkernelartifact": target = TargetKernelFile.objects.get(pk=artifact_id) file_name = target.file_name elif artifact_type == "targetsdkartifact": target = TargetSDKFile.objects.get(pk=artifact_id) file_name = target.file_name elif artifact_type == "licensemanifest": file_name = Target.objects.get(build = build, pk = artifact_id).license_manifest_path elif artifact_type == "packagemanifest": file_name = Target.objects.get(build = build, pk = artifact_id).package_manifest_path elif artifact_type == "tasklogfile": file_name = Task.objects.get(build = build, pk = artifact_id).logfile elif artifact_type == "logmessagefile": file_name = LogMessage.objects.get(build = build, pk = artifact_id).pathname if file_name and not response_file_name: response_file_name = os.path.basename(file_name) return (file_name, response_file_name) def build_artifact(request, build_id, artifact_type, artifact_id): """ View which returns a build artifact file as a response """ file_name = None response_file_name = None try: build = Build.objects.get(pk = build_id) file_name, response_file_name = _file_names_for_artifact( build, artifact_type, artifact_id ) if file_name and response_file_name: fsock = open(file_name, "rb") content_type = MimeTypeFinder.get_mimetype(file_name) response = HttpResponse(fsock, content_type = content_type) disposition = "attachment; filename=" + response_file_name response["Content-Disposition"] = disposition return response else: return toaster_render(request, "unavailable_artifact.html") except (ObjectDoesNotExist, IOError): return toaster_render(request, "unavailable_artifact.html")