blob: cc8517ba6c787a2ef657b9e88755282b81fb8375 [file] [log] [blame]
#
# 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
from toastermain.logs import log_view_mixin
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
@log_view_mixin
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
@log_view_mixin
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_str
class LazyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Promise):
return force_str(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('</', '<\\/')
def dirinfo(request, build_id, target_id, file_path=None):
template = "dirinfo.html"
objects = _get_dir_entries(build_id, target_id, '/')
packages_sum = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')).aggregate(Sum('installed_size'))
dir_list = None
if file_path is not None:
"""
Link from the included package detail file list page and is
requesting opening the dir info to a specific file path.
Provide the list of directories to expand and the full path to
highlight in the page.
"""
# Aassume target's path separator matches host's, that is, os.sep
sep = os.sep
dir_list = []
head = file_path
while head != sep:
(head, tail) = os.path.split(head)
if head != sep:
dir_list.insert(0, head)
build = Build.objects.get(pk=build_id)
context = { 'build': build,
'project': build.project,
'target': Target.objects.get(pk=target_id),
'packages_sum': packages_sum['installed_size__sum'],
'objects': objects,
'dir_list': dir_list,
'file_path': file_path,
}
return toaster_render(request, template, context)
def _find_task_dep(task_object):
tdeps = Task_Dependency.objects.filter(task=task_object).filter(depends_on__order__gt=0)
tdeps = tdeps.exclude(depends_on__outcome=Task.OUTCOME_NA).select_related("depends_on")
return [x.depends_on for x in tdeps]
def _find_task_revdep(task_object):
tdeps = Task_Dependency.objects.filter(depends_on=task_object).filter(task__order__gt=0)
tdeps = tdeps.exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
# exclude self-dependencies to prevent infinite dependency loop
# in generateCoveredList2()
tdeps = tdeps.exclude(task=task_object)
return [tdep.task for tdep in tdeps]
def _find_task_revdep_list(tasklist):
tdeps = Task_Dependency.objects.filter(depends_on__in=tasklist).filter(task__order__gt=0)
tdeps = tdeps.exclude(task__outcome=Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
# exclude self-dependencies to prevent infinite dependency loop
# in generateCoveredList2()
tdeps = tdeps.exclude(task=F('depends_on'))
return [tdep.task for tdep in tdeps]
def _find_task_provider(task_object):
task_revdeps = _find_task_revdep(task_object)
for tr in task_revdeps:
if tr.outcome != Task.OUTCOME_COVERED:
return tr
for tr in task_revdeps:
trc = _find_task_provider(tr)
if trc is not None:
return trc
return None
def configuration(request, build_id):
template = 'configuration.html'
var_names = ('BB_VERSION', 'BUILD_SYS', 'NATIVELSBSTRING', 'TARGET_SYS',
'MACHINE', 'DISTRO', 'DISTRO_VERSION', 'TUNE_FEATURES', 'TARGET_FPU')
context = dict(Variable.objects.filter(build=build_id, variable_name__in=var_names)\
.values_list('variable_name', 'variable_value'))
build = Build.objects.get(pk=build_id)
context.update({'objectname': 'configuration',
'object_search_display':'variables',
'filter_search_display':'variables',
'build': build,
'project': build.project,
'targets': Target.objects.filter(build=build_id)})
return toaster_render(request, template, context)
def configvars(request, build_id):
template = 'configvars.html'
(pagesize, orderby) = _get_parameters_values(request, 100, 'variable_name:+')
mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby, 'filter' : 'description__regex:.+' }
retval = _verify_parameters( request.GET, mandatory_parameters )
(filter_string, search_term, ordering_string) = _search_tuple(request, Variable)
if retval:
# if new search, clear the default filter
if search_term and len(search_term):
mandatory_parameters['filter']=''
return _redirect_parameters( 'configvars', request.GET, mandatory_parameters, build_id = build_id)
queryset = Variable.objects.filter(build=build_id).exclude(variable_name__istartswith='B_').exclude(variable_name__istartswith='do_')
queryset_with_search = _get_queryset(Variable, queryset, None, search_term, ordering_string, 'variable_name').exclude(variable_value='',vhistory__file_name__isnull=True)
queryset = _get_queryset(Variable, queryset, filter_string, search_term, ordering_string, 'variable_name')
# remove records where the value is empty AND there are no history files
queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True)
variables = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
# show all matching files (not just the last one)
file_filter= search_term + ":"
if filter_string.find('/conf/') > 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 <code>local.conf</code> and <code>bblayers.conf</code> configuration files inside the <code>/build/conf/</code> 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 <code>/conf/machine/</code> 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 <code>/conf/distro/</code> 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 <code>layer.conf</code> 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 <code>bitbake.conf</code> 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 <code>meta/conf/documentation.conf</code>'),
]
},
},
],
}
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'])
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=<QueryDict: {'setMachine': ['qemuarm']}>
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("<IMAGE>",default_target)
if "cancel" == cmnd:
if callback:
callback = callback.replace("<IMAGE>","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
@log_view_mixin
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) })
@log_view_mixin
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 disallowed and elsewhere-managed variables
vars_managed,vars_fstypes,vars_disallowed = 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_disallowed:
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")
@log_view_mixin
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_disallowed = {
'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_disallowed)
def projectconf(request, pid):
try:
prj = Project.objects.get(id = pid)
except Project.DoesNotExist:
return HttpResponseNotFound("<h1>Project id " + pid + " is unavailable</h1>")
# remove disallowed and externally managed varaibles from this list
vars_managed,vars_fstypes,vars_disallowed = 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_disallowed:
configvars = configvars.exclude(name = var)
context = {
'project': prj,
'configvars': configvars,
'vars_managed': vars_managed,
'vars_fstypes': vars_fstypes,
'vars_disallowed': vars_disallowed,
}
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")