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