views.py 82 KB

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