summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/toastergui/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/toaster/toastergui/views.py')
-rw-r--r--bitbake/lib/toaster/toastergui/views.py220
1 files changed, 201 insertions, 19 deletions
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 9a5e48e3bb..061e6436c8 100644
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -6,24 +6,36 @@
6# SPDX-License-Identifier: GPL-2.0-only 6# SPDX-License-Identifier: GPL-2.0-only
7# 7#
8 8
9import ast
9import re 10import re
11import subprocess
12import sys
13
14import bb.cooker
15from bb.ui import toasterui
16from bb.ui import eventreplay
10 17
11from django.db.models import F, Q, Sum 18from django.db.models import F, Q, Sum
12from django.db import IntegrityError 19from django.db import IntegrityError
13from django.shortcuts import render, redirect, get_object_or_404 20from django.shortcuts import render, redirect, get_object_or_404, HttpResponseRedirect
14from django.utils.http import urlencode 21from django.utils.http import urlencode
15from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe 22from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe
16from orm.models import LogMessage, Variable, Package_Dependency, Package 23from orm.models import LogMessage, Variable, Package_Dependency, Package
17from orm.models import Task_Dependency, Package_File 24from orm.models import Task_Dependency, Package_File
18from orm.models import Target_Installed_Package, Target_File 25from orm.models import Target_Installed_Package, Target_File
19from orm.models import TargetKernelFile, TargetSDKFile, Target_Image_File 26from orm.models import TargetKernelFile, TargetSDKFile, Target_Image_File
20from orm.models import BitbakeVersion, CustomImageRecipe 27from orm.models import BitbakeVersion, CustomImageRecipe, EventLogsImports
21 28
22from django.urls import reverse, resolve 29from django.urls import reverse, resolve
30from django.contrib import messages
31
23from django.core.exceptions import ObjectDoesNotExist 32from django.core.exceptions import ObjectDoesNotExist
33from django.core.files.storage import FileSystemStorage
34from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile
24from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 35from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
25from django.http import HttpResponseNotFound, JsonResponse 36from django.http import HttpResponseNotFound, JsonResponse
26from django.utils import timezone 37from django.utils import timezone
38from django.views.generic import TemplateView
27from datetime import timedelta, datetime 39from datetime import timedelta, datetime
28from toastergui.templatetags.projecttags import json as jsonfilter 40from toastergui.templatetags.projecttags import json as jsonfilter
29from decimal import Decimal 41from decimal import Decimal
@@ -32,13 +44,20 @@ import os
32from os.path import dirname 44from os.path import dirname
33import mimetypes 45import mimetypes
34 46
47from toastergui.forms import LoadFileForm
48
49from collections import namedtuple
50
35import logging 51import logging
36 52
53from toastermain.logs import log_view_mixin
54
37logger = logging.getLogger("toaster") 55logger = logging.getLogger("toaster")
38 56
39# Project creation and managed build enable 57# Project creation and managed build enable
40project_enable = ('1' == os.environ.get('TOASTER_BUILDSERVER')) 58project_enable = ('1' == os.environ.get('TOASTER_BUILDSERVER'))
41is_project_specific = ('1' == os.environ.get('TOASTER_PROJECTSPECIFIC')) 59is_project_specific = ('1' == os.environ.get('TOASTER_PROJECTSPECIFIC'))
60import_page = False
42 61
43class MimeTypeFinder(object): 62class MimeTypeFinder(object):
44 # setting this to False enables additional non-standard mimetypes 63 # setting this to False enables additional non-standard mimetypes
@@ -56,6 +75,7 @@ class MimeTypeFinder(object):
56 return guessed_type 75 return guessed_type
57 76
58# single point to add global values into the context before rendering 77# single point to add global values into the context before rendering
78@log_view_mixin
59def toaster_render(request, page, context): 79def toaster_render(request, page, context):
60 context['project_enable'] = project_enable 80 context['project_enable'] = project_enable
61 context['project_specific'] = is_project_specific 81 context['project_specific'] = is_project_specific
@@ -352,7 +372,6 @@ def _get_parameters_values(request, default_count, default_order):
352# set cookies for parameters. this is usefull in case parameters are set 372# set cookies for parameters. this is usefull in case parameters are set
353# manually from the GET values of the link 373# manually from the GET values of the link
354def _set_parameters_values(pagesize, orderby, request): 374def _set_parameters_values(pagesize, orderby, request):
355 from django.urls import resolve
356 current_url = resolve(request.path_info).url_name 375 current_url = resolve(request.path_info).url_name
357 request.session['%s_count' % current_url] = pagesize 376 request.session['%s_count' % current_url] = pagesize
358 request.session['%s_orderby' % current_url] =orderby 377 request.session['%s_orderby' % current_url] =orderby
@@ -665,20 +684,20 @@ def recipe_packages(request, build_id, recipe_id):
665 return response 684 return response
666 685
667from django.http import HttpResponse 686from django.http import HttpResponse
687@log_view_mixin
668def xhr_dirinfo(request, build_id, target_id): 688def xhr_dirinfo(request, build_id, target_id):
669 top = request.GET.get('start', '/') 689 top = request.GET.get('start', '/')
670 return HttpResponse(_get_dir_entries(build_id, target_id, top), content_type = "application/json") 690 return HttpResponse(_get_dir_entries(build_id, target_id, top), content_type = "application/json")
671 691
672from django.utils.functional import Promise 692from django.utils.functional import Promise
673from django.utils.encoding import force_text 693from django.utils.encoding import force_str
674class LazyEncoder(json.JSONEncoder): 694class LazyEncoder(json.JSONEncoder):
675 def default(self, obj): 695 def default(self, obj):
676 if isinstance(obj, Promise): 696 if isinstance(obj, Promise):
677 return force_text(obj) 697 return force_str(obj)
678 return super(LazyEncoder, self).default(obj) 698 return super(LazyEncoder, self).default(obj)
679 699
680from toastergui.templatetags.projecttags import filtered_filesizeformat 700from toastergui.templatetags.projecttags import filtered_filesizeformat
681import os
682def _get_dir_entries(build_id, target_id, start): 701def _get_dir_entries(build_id, target_id, start):
683 node_str = { 702 node_str = {
684 Target_File.ITYPE_REGULAR : '-', 703 Target_File.ITYPE_REGULAR : '-',
@@ -1404,7 +1423,7 @@ if True:
1404 if not os.path.isdir('%s/conf' % request.POST['importdir']): 1423 if not os.path.isdir('%s/conf' % request.POST['importdir']):
1405 raise BadParameterException("Bad path or missing 'conf' directory (%s)" % request.POST['importdir']) 1424 raise BadParameterException("Bad path or missing 'conf' directory (%s)" % request.POST['importdir'])
1406 from django.core import management 1425 from django.core import management
1407 management.call_command('buildimport', '--command=import', '--name=%s' % request.POST['projectname'], '--path=%s' % request.POST['importdir'], interactive=False) 1426 management.call_command('buildimport', '--command=import', '--name=%s' % request.POST['projectname'], '--path=%s' % request.POST['importdir'])
1408 prj = Project.objects.get(name = request.POST['projectname']) 1427 prj = Project.objects.get(name = request.POST['projectname'])
1409 prj.merged_attr = True 1428 prj.merged_attr = True
1410 prj.save() 1429 prj.save()
@@ -1606,12 +1625,13 @@ if True:
1606 # make sure we have a machine set for this project 1625 # make sure we have a machine set for this project
1607 ProjectVariable.objects.get_or_create(project=new_project, 1626 ProjectVariable.objects.get_or_create(project=new_project,
1608 name="MACHINE", 1627 name="MACHINE",
1609 value="qemux86") 1628 value="qemux86-64")
1610 context = {'project': new_project} 1629 context = {'project': new_project}
1611 return toaster_render(request, "js-unit-tests.html", context) 1630 return toaster_render(request, "js-unit-tests.html", context)
1612 1631
1613 from django.views.decorators.csrf import csrf_exempt 1632 from django.views.decorators.csrf import csrf_exempt
1614 @csrf_exempt 1633 @csrf_exempt
1634 @log_view_mixin
1615 def xhr_testreleasechange(request, pid): 1635 def xhr_testreleasechange(request, pid):
1616 def response(data): 1636 def response(data):
1617 return HttpResponse(jsonfilter(data), 1637 return HttpResponse(jsonfilter(data),
@@ -1648,6 +1668,7 @@ if True:
1648 except Exception as e: 1668 except Exception as e:
1649 return response({"error": str(e) }) 1669 return response({"error": str(e) })
1650 1670
1671 @log_view_mixin
1651 def xhr_configvaredit(request, pid): 1672 def xhr_configvaredit(request, pid):
1652 try: 1673 try:
1653 prj = Project.objects.get(id = pid) 1674 prj = Project.objects.get(id = pid)
@@ -1683,12 +1704,12 @@ if True:
1683 t=request.POST['configvarDel'].strip() 1704 t=request.POST['configvarDel'].strip()
1684 pt = ProjectVariable.objects.get(pk = int(t)).delete() 1705 pt = ProjectVariable.objects.get(pk = int(t)).delete()
1685 1706
1686 # return all project settings, filter out blacklist and elsewhere-managed variables 1707 # return all project settings, filter out disallowed and elsewhere-managed variables
1687 vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context() 1708 vars_managed,vars_fstypes,vars_disallowed = get_project_configvars_context()
1688 configvars_query = ProjectVariable.objects.filter(project_id = pid).all() 1709 configvars_query = ProjectVariable.objects.filter(project_id = pid).all()
1689 for var in vars_managed: 1710 for var in vars_managed:
1690 configvars_query = configvars_query.exclude(name = var) 1711 configvars_query = configvars_query.exclude(name = var)
1691 for var in vars_blacklist: 1712 for var in vars_disallowed:
1692 configvars_query = configvars_query.exclude(name = var) 1713 configvars_query = configvars_query.exclude(name = var)
1693 1714
1694 return_data = { 1715 return_data = {
@@ -1708,7 +1729,7 @@ if True:
1708 except ProjectVariable.DoesNotExist: 1729 except ProjectVariable.DoesNotExist:
1709 pass 1730 pass
1710 try: 1731 try:
1711 return_data['image_install_append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL_append").value, 1732 return_data['image_install:append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL:append").value,
1712 except ProjectVariable.DoesNotExist: 1733 except ProjectVariable.DoesNotExist:
1713 pass 1734 pass
1714 try: 1735 try:
@@ -1726,6 +1747,7 @@ if True:
1726 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json") 1747 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1727 1748
1728 1749
1750 @log_view_mixin
1729 def customrecipe_download(request, pid, recipe_id): 1751 def customrecipe_download(request, pid, recipe_id):
1730 recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id) 1752 recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id)
1731 1753
@@ -1781,7 +1803,7 @@ if True:
1781 'MACHINE', 'BBLAYERS' 1803 'MACHINE', 'BBLAYERS'
1782 } 1804 }
1783 1805
1784 vars_blacklist = { 1806 vars_disallowed = {
1785 'PARALLEL_MAKE','BB_NUMBER_THREADS', 1807 'PARALLEL_MAKE','BB_NUMBER_THREADS',
1786 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT', 1808 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT',
1787 'PARALLEL_MAKE','TMPDIR', 1809 'PARALLEL_MAKE','TMPDIR',
@@ -1790,7 +1812,7 @@ if True:
1790 1812
1791 vars_fstypes = Target_Image_File.SUFFIXES 1813 vars_fstypes = Target_Image_File.SUFFIXES
1792 1814
1793 return(vars_managed,sorted(vars_fstypes),vars_blacklist) 1815 return(vars_managed,sorted(vars_fstypes),vars_disallowed)
1794 1816
1795 def projectconf(request, pid): 1817 def projectconf(request, pid):
1796 1818
@@ -1799,12 +1821,12 @@ if True:
1799 except Project.DoesNotExist: 1821 except Project.DoesNotExist:
1800 return HttpResponseNotFound("<h1>Project id " + pid + " is unavailable</h1>") 1822 return HttpResponseNotFound("<h1>Project id " + pid + " is unavailable</h1>")
1801 1823
1802 # remove blacklist and externally managed varaibles from this list 1824 # remove disallowed and externally managed varaibles from this list
1803 vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context() 1825 vars_managed,vars_fstypes,vars_disallowed = get_project_configvars_context()
1804 configvars = ProjectVariable.objects.filter(project_id = pid).all() 1826 configvars = ProjectVariable.objects.filter(project_id = pid).all()
1805 for var in vars_managed: 1827 for var in vars_managed:
1806 configvars = configvars.exclude(name = var) 1828 configvars = configvars.exclude(name = var)
1807 for var in vars_blacklist: 1829 for var in vars_disallowed:
1808 configvars = configvars.exclude(name = var) 1830 configvars = configvars.exclude(name = var)
1809 1831
1810 context = { 1832 context = {
@@ -1812,7 +1834,7 @@ if True:
1812 'configvars': configvars, 1834 'configvars': configvars,
1813 'vars_managed': vars_managed, 1835 'vars_managed': vars_managed,
1814 'vars_fstypes': vars_fstypes, 1836 'vars_fstypes': vars_fstypes,
1815 'vars_blacklist': vars_blacklist, 1837 'vars_disallowed': vars_disallowed,
1816 } 1838 }
1817 1839
1818 try: 1840 try:
@@ -1839,7 +1861,7 @@ if True:
1839 except ProjectVariable.DoesNotExist: 1861 except ProjectVariable.DoesNotExist:
1840 pass 1862 pass
1841 try: 1863 try:
1842 context['image_install_append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL_append").value 1864 context['image_install:append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL:append").value
1843 context['image_install_append_defined'] = "1" 1865 context['image_install_append_defined'] = "1"
1844 except ProjectVariable.DoesNotExist: 1866 except ProjectVariable.DoesNotExist:
1845 pass 1867 pass
@@ -1933,3 +1955,163 @@ if True:
1933 except (ObjectDoesNotExist, IOError): 1955 except (ObjectDoesNotExist, IOError):
1934 return toaster_render(request, "unavailable_artifact.html") 1956 return toaster_render(request, "unavailable_artifact.html")
1935 1957
1958
1959class CommandLineBuilds(TemplateView):
1960 model = EventLogsImports
1961 template_name = 'command_line_builds.html'
1962
1963 def get_context_data(self, **kwargs):
1964 context = super(CommandLineBuilds, self).get_context_data(**kwargs)
1965 #get value from BB_DEFAULT_EVENTLOG defined in bitbake.conf
1966 eventlog = subprocess.check_output(['bitbake-getvar', 'BB_DEFAULT_EVENTLOG', '--value'])
1967 if eventlog:
1968 logs_dir = os.path.dirname(eventlog.decode().strip('\n'))
1969 files = os.listdir(logs_dir)
1970 imported_files = EventLogsImports.objects.all()
1971 files_list = []
1972
1973 # Filter files that end with ".json"
1974 event_files = []
1975 for file in files:
1976 if file.endswith(".json"):
1977 # because BB_DEFAULT_EVENTLOG is a directory, we need to check if the file is a valid eventlog
1978 with open("{}/{}".format(logs_dir, file)) as efile:
1979 content = efile.read()
1980 if 'allvariables' in content:
1981 event_files.append(file)
1982
1983 #build dict for template using db data
1984 for event_file in event_files:
1985 if imported_files.filter(name=event_file):
1986 files_list.append({
1987 'name': event_file,
1988 'imported': True,
1989 'build_id': imported_files.filter(name=event_file)[0].build_id,
1990 'size': os.path.getsize("{}/{}".format(logs_dir, event_file))
1991 })
1992 else:
1993 files_list.append({
1994 'name': event_file,
1995 'imported': False,
1996 'build_id': None,
1997 'size': os.path.getsize("{}/{}".format(logs_dir, event_file))
1998 })
1999 context['import_all'] = True
2000
2001 context['files'] = files_list
2002 context['dir'] = logs_dir
2003 else:
2004 context['files'] = []
2005 context['dir'] = ''
2006
2007 # enable session variable
2008 if not self.request.session.get('file'):
2009 self.request.session['file'] = ""
2010
2011 context['form'] = LoadFileForm()
2012 context['project_enable'] = project_enable
2013 return context
2014
2015 def post(self, request, **kwargs):
2016 logs_dir = request.POST.get('dir')
2017 all_files = request.POST.get('all')
2018
2019 # check if a build is already in progress
2020 if Build.objects.filter(outcome=Build.IN_PROGRESS):
2021 messages.add_message(
2022 self.request,
2023 messages.ERROR,
2024 "A build is already in progress. Please wait for it to complete before starting a new build."
2025 )
2026 return JsonResponse({'response': 'building'})
2027 imported_files = EventLogsImports.objects.all()
2028 try:
2029 if all_files == 'true':
2030 # use of session variable to deactivate icon for builds in progress
2031 request.session['all_builds'] = True
2032 request.session.modified = True
2033 request.session.save()
2034
2035 files = ast.literal_eval(request.POST.get('file'))
2036 for file in files:
2037 if imported_files.filter(name=file.get('name')).exists():
2038 imported_files.filter(name=file.get('name'))[0].imported = True
2039 else:
2040 with open("{}/{}".format(logs_dir, file.get('name'))) as eventfile:
2041 # load variables from the first line
2042 variables = None
2043 while line := eventfile.readline().strip():
2044 try:
2045 variables = json.loads(line)['allvariables']
2046 break
2047 except (KeyError, json.JSONDecodeError):
2048 continue
2049 if not variables:
2050 raise Exception("File content missing build variables")
2051 eventfile.seek(0)
2052 params = namedtuple('ConfigParams', ['observe_only'])(True)
2053 player = eventreplay.EventPlayer(eventfile, variables)
2054
2055 toasterui.main(player, player, params)
2056 event_log_import = EventLogsImports.objects.create(name=file.get('name'), imported=True)
2057 event_log_import.build_id = Build.objects.last().id
2058 event_log_import.save()
2059 else:
2060 if self.request.FILES.get('eventlog_file'):
2061 file = self.request.FILES['eventlog_file']
2062 else:
2063 file = request.POST.get('file')
2064 # use of session variable to deactivate icon for build in progress
2065 request.session['file'] = file
2066 request.session['all_builds'] = False
2067 request.session.modified = True
2068 request.session.save()
2069
2070 if imported_files.filter(name=file).exists():
2071 imported_files.filter(name=file)[0].imported = True
2072 else:
2073 if isinstance(file, InMemoryUploadedFile) or isinstance(file, TemporaryUploadedFile):
2074 variables = None
2075 while line := file.readline().strip():
2076 try:
2077 variables = json.loads(line)['allvariables']
2078 break
2079 except (KeyError, json.JSONDecodeError):
2080 continue
2081 if not variables:
2082 raise Exception("File content missing build variables")
2083 file.seek(0)
2084 params = namedtuple('ConfigParams', ['observe_only'])(True)
2085 player = eventreplay.EventPlayer(file, variables)
2086 if not os.path.exists('{}/{}'.format(logs_dir, file.name)):
2087 fs = FileSystemStorage(location=logs_dir)
2088 fs.save(file.name, file)
2089 toasterui.main(player, player, params)
2090 else:
2091 with open("{}/{}".format(logs_dir, file)) as eventfile:
2092 # load variables from the first line
2093 variables = None
2094 while line := eventfile.readline().strip():
2095 try:
2096 variables = json.loads(line)['allvariables']
2097 break
2098 except (KeyError, json.JSONDecodeError):
2099 continue
2100 if not variables:
2101 raise Exception("File content missing build variables")
2102 eventfile.seek(0)
2103 params = namedtuple('ConfigParams', ['observe_only'])(True)
2104 player = eventreplay.EventPlayer(eventfile, variables)
2105 toasterui.main(player, player, params)
2106 event_log_import = EventLogsImports.objects.create(name=file, imported=True)
2107 event_log_import.build_id = Build.objects.last().id
2108 event_log_import.save()
2109 request.session['file'] = ""
2110 except Exception:
2111 messages.add_message(
2112 self.request,
2113 messages.ERROR,
2114 "The file content is not in the correct format. Update file content or upload a different file."
2115 )
2116 return HttpResponseRedirect("/toastergui/cmdline/")
2117 return HttpResponseRedirect('/toastergui/builds/')