Toaster patches
cover Toaster patches
Various bug fixes and pylint fixes. Reviewed by Belén and Michael.
The following changes since commit c348fa50186fb0796bc7b793c4b8d710419f2a0d:
bitbake: toaster: move code from setup_lv_tests to setUp (2015-08-17 14:41:45 +0100)
are available in the git repository at:
git://git.yoctoproject.org/poky-contrib michaelw/toaster/submit/adamian/20150817_bug_fixes_poky
http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=michaelw/toaster/submit/adamian/20150817_bug_fixes_poky
Alexandru DAMIAN (13):
bitbake: toaster: pylint fixes
bitbake: toaster: checksettings: fix guesspath initialization
bitbake: toaster: checksettings: fix TEMPLATECONF detection
bitbake: toaster: improve exception handling
bitbake: toaster: models field initialization
bitbake: toaster: use loggers instead of prints
bitbake: toastergui: rename variables in views.py
bitbake: toaster: do not stop data import on bad data
bitbake: toaster: replace raising Exceptions in loadconf
bitbake: toaster: fix updates on failed build requests
bitbake: toaster logger: fix pylint issues
toastergui: do not show in-progress builds in the table
toastergui: fix projectbuilds page
Alexandru Damian (1):
bitbake: toaster: fix pylint errors
lib/bb/ui/buildinfohelper.py | 130 ++++++++------- lib/bb/ui/toasterui.py | 62 ++++--- lib/toaster/bldcontrol/bbcontroller.py | 2 +- .../management/commands/checksettings.py | 15 +- .../bldcontrol/management/commands/loadconf.py | 37 +++-- .../bldcontrol/management/commands/runbuilds.py | 2 +- lib/toaster/orm/models.py | 80 ++++----- .../toastergui/templates/projectbuilds.html | 42 +---- lib/toaster/toastergui/views.py | 183 +++++++++++---------- lib/toaster/toastergui/widgets.py | 3 + .../toastermain/management/commands/perf.py | 7 +- 11 files changed, 293 insertions(+), 270 deletions(-)
Signature · 1 line
2.1.4
PATCH 1/14 bitbake: toaster: pylint fixes
From: Alexandru DAMIAN <[email protected]>
This patch fixes imports, default parameters to functions, incorrect overloading and exception handling issues highlighted by pylint. There are no functional changes.
--- .../management/commands/checksettings.py | 3 +- lib/toaster/orm/models.py | 1 - lib/toaster/toastergui/views.py | 32 +++++++++------------- 3 files changed, 15 insertions(+), 21 deletions(-)
lib/toaster/bldcontrol/management/commands/checksettings.py +2 -1
@@ -4,6 +4,7 @@ from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdExcep from bldcontrol.models import BuildRequest, BuildEnvironment, BRError from orm.models import ToasterSetting, Build import os +import sys, traceback def DN(path): if path is None:
@@ -228,7 +229,7 @@ class Command(NoArgsCommand): - def handle(self, **options): + def handle_noargs(self, **options): retval = 0 retval += self._verify_artifact_storage_dir() retval += self._verify_build_environment()
lib/toaster/orm/models.py +0 -1
@@ -807,7 +807,6 @@ class LayerIndexLayerSource(LayerSource): Fetches layer, recipe and machine information from remote repository """ assert self.apiurl is not None - from django.db import IntegrityError from django.db import transaction, connection import httplib, urlparse, json
lib/toaster/toastergui/views.py +13 -19
@@ -102,8 +102,7 @@ def _project_recent_build_list(prj): def objtojson(obj): from django.db.models.query import QuerySet - from django.db.models import Model, IntegerField - from django.db.models.fields.related import ForeignKey + from django.db.models import Model if isinstance(obj, datetime): return obj.isoformat()
@@ -266,8 +265,8 @@ def _get_filtering_query(filter_string): return reduce(operator.and_, [k for k in and_query]) -def _get_toggle_order(request, orderkey, reverse = False): - if reverse: +def _get_toggle_order(request, orderkey, toggle_reverse = False): + if toggle_reverse: return "%s:+" % orderkey if request.GET.get('orderby', "") == "%s:-" % orderkey else "%s:-" % orderkey else: return "%s:-" % orderkey if request.GET.get('orderby', "") == "%s:+" % orderkey else "%s:+" % orderkey
@@ -522,7 +521,9 @@ def builddashboard( request, build_id ): -def generateCoveredList2( revlist = [] ): +def generateCoveredList2( revlist = None ): + if revlist is None: + revlist = [] covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ] while len(covered_list): revlist = [ x for x in revlist if x.outcome != Task.OUTCOME_COVERED ]
@@ -910,9 +911,7 @@ def _get_dir_entries(build_id, target_id, start): except Exception as e: print "Exception ", e - import traceback traceback.print_exc(e) - pass # sort by directories first, then by name rsorted = sorted(response, key=lambda entry : entry['name'])
@@ -936,7 +935,7 @@ def dirinfo(request, build_id, target_id, file_path=None): dir_list = [] head = file_path while head != sep: - (head,tail) = os.path.split(head) + (head, tail) = os.path.split(head) if head != sep: dir_list.insert(0, head)
@@ -1837,7 +1836,6 @@ def image_information_dir(request, build_id, target_id, packagefile_id): def managedcontextprocessor(request): - import subprocess ret = { "projects": Project.objects.all(), "DEBUG" : toastermain.settings.DEBUG,
@@ -1865,13 +1863,9 @@ if True: import traceback - class BadParameterException(Exception): pass # error thrown on invalid POST requests - - - class InvalidRequestException(Exception): - def __init__(self, response): - self.response = response - + class BadParameterException(Exception): + ''' The exception raised on invalid POST requests ''' + pass # shows the "all builds" page for managed mode; it displays build requests (at least started!) instead of actual builds @_template_renderer("builds.html")
@@ -2474,7 +2468,7 @@ if True: try: layer_version_id = request.POST["layer_version_id"] layer_version = Layer_Version.objects.get(id=layer_version_id) - except: + except Layer_Version.DoesNotExist: return error_response("Cannot find layer to update")
@@ -2503,8 +2497,8 @@ if True: try: layer_version.layer.save() layer_version.save() - except: - return error_response("Could not update layer version entry") + except Exception as e: + return error_response("Could not update layer version entry: %s" % e) return HttpResponse(jsonfilter({"error": "ok",}), content_type = "application/json")
Signature · 1 line
2.1.4
PATCH 2/14 bitbake: toaster: checksettings: fix guesspath initialization
From: Alexandru DAMIAN <[email protected]>
Object fields need to be instantiated in the constructor. Fixed prompted by pylint.
--- lib/toaster/bldcontrol/management/commands/checksettings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
lib/toaster/bldcontrol/management/commands/checksettings.py +3 -1
@@ -17,6 +17,9 @@ class Command(NoArgsCommand): args = "" help = "Verifies that the configured settings are valid and usable, or prompts the user to fix the settings." + def __init__(self, *args, **kwargs): + super(Command, self).__init__(*args, **kwargs) + self.guesspath = DN(DN(DN(DN(DN(DN(DN(__file__))))))) def _find_first_path_for_file(self, startdirectory, filename, level = 0): if level < 0:
@@ -83,7 +86,6 @@ class Command(NoArgsCommand): def _verify_build_environment(self): - self.guesspath = DN(DN(DN(DN(DN(DN(DN(__file__))))))) # refuse to start if we have no build environments while BuildEnvironment.objects.count() == 0: print(" !! No build environments found. Toaster needs at least one build environment in order to be able to run builds.\n" +
Signature · 1 line
2.1.4
PATCH 3/14 bitbake: toaster: checksettings: fix TEMPLATECONF detection
In reply to Michael WoodFrom: Alexandru DAMIAN <[email protected]>
Improving the TEMPLATECONF detection by verifying the return code and dumping extra debug information in case of exception.
--- lib/toaster/bldcontrol/management/commands/checksettings.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
lib/toaster/bldcontrol/management/commands/checksettings.py +7 -1
@@ -166,7 +166,12 @@ class Command(NoArgsCommand): for dirname in self._recursive_list_directories(be.sourcedir,2): if os.path.exists(os.path.join(dirname, ".templateconf")): import subprocess - conffilepath, error = subprocess.Popen('bash -c ". '+os.path.join(dirname, ".templateconf")+'; echo \"\$TEMPLATECONF\""', shell=True, stdout=subprocess.PIPE).communicate() + proc = subprocess.Popen('bash -c ". '+os.path.join(dirname, ".templateconf")+'; echo \"\$TEMPLATECONF\""', shell=True, stdout=subprocess.PIPE) + conffilepath, stderroroutput = proc.communicate() + proc.wait() + if proc.returncode != 0: + raise Exception("Failed to source TEMPLATECONF: %s" % stderroroutput) + conffilepath = os.path.join(conffilepath.strip(), "toasterconf.json") candidatefilepath = os.path.join(dirname, conffilepath) if "toaster_cloned" in candidatefilepath:
@@ -195,6 +200,7 @@ class Command(NoArgsCommand): return is_changed except Exception as e: print "Failure while trying to import the toaster config file: %s" % e + traceback.print_exc(e) else: print "\n Toaster could not find a configuration file. You need to configure Toaster manually using the web interface, or create a configuration file and use\n bitbake/lib/toaster/managepy.py loadconf [filename]\n command to load it. You can use https://wiki.yoctoproject.org/wiki/File:Toasterconf.json.txt.patch as a starting point."
Signature · 1 line
2.1.4
PATCH 4/14 bitbake: toaster: improve exception handling
In reply to Michael WoodFrom: Alexandru DAMIAN <[email protected]>
This patch improves exception handling, fixing issues as prompted by pylint, including exception renaming.
--- lib/toaster/orm/models.py | 15 ++++++--------- lib/toaster/toastergui/views.py | 13 ++++--------- 2 files changed, 10 insertions(+), 18 deletions(-)
lib/toaster/orm/models.py +6 -9
@@ -269,11 +269,10 @@ class Project(models.Model): except ProjectVariable.DoesNotExist: pass br.save() - except Exception as e: + except Exception: + # revert the build request creation since we're not done cleanly br.delete() - import sys - et, ei, tb = sys.exc_info() - raise type(e), e, tb + raise return br class Build(models.Model):
@@ -364,7 +363,7 @@ class BuildArtifact(models.Model): def is_available(self): - return build.buildrequest.environment.has_artifact(file_path) + return self.build.buildrequest.environment.has_artifact(self.file_name) class ProjectTarget(models.Model): project = models.ForeignKey(Project)
@@ -981,12 +980,10 @@ class LayerIndexLayerSource(LayerSource): if 'inherits' in ri: ro.is_image = 'image' in ri['inherits'].split() ro.save() - except: - #print "Duplicate Recipe, ignoring: ", vars(ro) - pass + except Exception: + logger.warning("Duplicate Recipe, ignoring: %s " % vars(ro)) if not connection.features.autocommits_when_autocommit_is_off: transaction.set_autocommit(True) - pass class BitbakeVersion(models.Model):
lib/toaster/toastergui/views.py +4 -9
@@ -1876,10 +1876,7 @@ if True: queryset = Build.objects.filter(outcome__lte = Build.IN_PROGRESS) - try: - context, pagesize, orderby = _build_list_helper(request, queryset) - except InvalidRequestException as e: - raise RedirectException( builds, request.GET, e.response) + context, pagesize, orderby = _build_list_helper(request, queryset) _set_parameters_values(pagesize, orderby, request) return context
@@ -2422,7 +2419,8 @@ if True: # dependency already (like modified on another page) try: prj_layer, prj_layer_created = ProjectLayer.objects.get_or_create(layercommit=layer_dep_obj, project=prj) - except: + except IntegrityError as e: + logger.warning("Integrity error while saving Project Layers: %s (original %s)" % (e, e.__cause__)) continue if prj_layer_created:
@@ -2639,10 +2637,7 @@ if True: queryset = Build.objects.filter(outcome__lte = Build.IN_PROGRESS) - try: - context, pagesize, orderby = _build_list_helper(request, queryset) - except InvalidRequestException as e: - raise RedirectException('projectbuilds', request.GET, e.response, pid = pid) + context, pagesize, orderby = _build_list_helper(request, queryset) context['project'] = prj _set_parameters_values(pagesize, orderby, request)
Signature · 1 line
2.1.4
PATCH 5/14 bitbake: toaster: models field initialization
In reply to Michael WoodFrom: Alexandru DAMIAN <[email protected]>
As prompted by pylint, the object fields need to be initialized properly, so this patch fixes that.
Also adds some casts to int, because, sometimes, the IntegerField is not returning an int, but a string.
--- lib/toaster/orm/models.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-)
lib/toaster/orm/models.py +21 -17
@@ -472,20 +472,27 @@ class Task(models.Model): search_allowed_fields = [ "recipe__name", "recipe__version", "task_name", "logfile" ] + def __init__(self, *args, **kwargs): + super(Task, self).__init__(*args, **kwargs) + try: + self._helptext = HelpText.objects.get(key=self.task_name, area=HelpText.VARIABLE, build=self.build).text + except HelpText.DoesNotExist: + self._helptext = None + def get_related_setscene(self): return Task.objects.filter(task_executed=True, build = self.build, recipe = self.recipe, task_name=self.task_name+"_setscene") def get_outcome_text(self): - return Task.TASK_OUTCOME[self.outcome + 1][1] + return Task.TASK_OUTCOME[int(self.outcome) + 1][1] def get_outcome_help(self): - return Task.TASK_OUTCOME_HELP[self.outcome][1] + return Task.TASK_OUTCOME_HELP[int(self.outcome)][1] def get_sstate_text(self): if self.sstate_result==Task.SSTATE_NA: return '' else: - return Task.SSTATE_RESULT[self.sstate_result][1] + return Task.SSTATE_RESULT[int(self.sstate_result)][1] def get_executed_display(self): if self.task_executed:
@@ -493,13 +500,6 @@ class Task(models.Model): return "Not Executed" def get_description(self): - if '_helptext' in vars(self) and self._helptext != None: - return self._helptext - try: - self._helptext = HelpText.objects.get(key=self.task_name, area=HelpText.VARIABLE, build=self.build).text - except HelpText.DoesNotExist: - self._helptext = None - return self._helptext build = models.ForeignKey(Build, related_name='task_build')
@@ -721,13 +721,8 @@ class LayerSource(models.Model): sourcetype = models.IntegerField(choices=SOURCE_TYPE) apiurl = models.CharField(max_length=255, null=True, default=None) - def update(self): - """ - Updates the local database information from the upstream layer source - """ - raise Exception("Abstract, update() must be implemented by all LayerSource-derived classes (object is %s)" % str(vars(self))) - - def save(self, *args, **kwargs): + def __init__(self, *args, **kwargs): + super(LayerSource, self).__init__(*args, **kwargs) if self.sourcetype == LayerSource.TYPE_LOCAL: self.__class__ = LocalLayerSource elif self.sourcetype == LayerSource.TYPE_LAYERINDEX:
@@ -736,6 +731,15 @@ class LayerSource(models.Model): self.__class__ = ImportedLayerSource elif self.sourcetype == None: raise Exception("Unknown LayerSource-derived class. If you added a new layer source type, fill out all code stubs.") + + + def update(self): + """ + Updates the local database information from the upstream layer source + """ + raise Exception("Abstract, update() must be implemented by all LayerSource-derived classes (object is %s)" % str(vars(self))) + + def save(self, *args, **kwargs): return super(LayerSource, self).save(*args, **kwargs) def get_object(self):
Signature · 1 line
2.1.4
PATCH 6/14 bitbake: toaster: use loggers instead of prints
In reply to Michael WoodFrom: Alexandru DAMIAN <[email protected]>
Switching debugging from using print statement to using loggers, as it uses the logging infrastructure to process the messages.
The messages are logged with the "toaster" logger which is defined in the toastermain/settings.py.
--- lib/toaster/orm/models.py | 22 +++++++++++++--------- lib/toaster/toastergui/views.py | 4 ++++ 2 files changed, 17 insertions(+), 9 deletions(-)
lib/toaster/orm/models.py +13 -9
@@ -29,6 +29,11 @@ from django.core import validators from django.conf import settings import django.db.models.signals + +import logging +logger = logging.getLogger("toaster") + + class GitURLValidator(validators.URLValidator): import re regex = re.compile(
@@ -855,8 +860,8 @@ class LayerIndexLayerSource(LayerSource): except Exception as e: import traceback if proxy_settings is not None: - print "EE: Using proxy ", proxy_settings - print "EE: could not connect to %s, skipping update: %s\n%s" % (self.apiurl, e, traceback.format_exc(e)) + logger.info("EE: Using proxy %s" % proxy_settings) + logger.warning("EE: could not connect to %s, skipping update: %s\n%s" % (self.apiurl, e, traceback.format_exc(e))) return # update branches; only those that we already have names listed in the
@@ -865,7 +870,7 @@ class LayerIndexLayerSource(LayerSource): if len(whitelist_branch_names) == 0: raise Exception("Failed to make list of branches to fetch") - print "Fetching branches" + logger.debug("Fetching branches") branches_info = _get_json_response(apilinks['branches'] + "?filter=name:%s" % "OR".join(whitelist_branch_names)) for bi in branches_info:
@@ -895,7 +900,7 @@ class LayerIndexLayerSource(LayerSource): transaction.set_autocommit(True) # update layerbranches/layer_versions - print "Fetching layer information" + logger.debug("Fetching layer information") layerbranches_info = _get_json_response(apilinks['layerBranches'] + "?filter=branch:%s" % "OR".join(map(lambda x: str(x.up_id), [i for i in Branch.objects.filter(layer_source = self) if i.up_id is not None] )) )
@@ -933,7 +938,7 @@ class LayerIndexLayerSource(LayerSource): try: dependlist[lv].append(Layer_Version.objects.get(layer_source = self, layer__up_id = ldi['dependency'], up_branch = lv.up_branch)) except Layer_Version.DoesNotExist: - print "Cannot find layer version ", self, ldi['dependency'], lv.up_branch + logger.warning("Cannot find layer version %s dep:%s up_brach:%s" % (self, ldi['dependency'], lv.up_branch)) for lv in dependlist: LayerVersionDependency.objects.filter(layer_version = lv).delete()
@@ -944,7 +949,7 @@ class LayerIndexLayerSource(LayerSource): # update machines - print "Fetching machine information" + logger.debug("Fetching machine information") machines_info = _get_json_response(apilinks['machines'] + "?filter=layerbranch:%s" % "OR".join(map(lambda x: str(x.up_id), Layer_Version.objects.filter(layer_source = self))) )
@@ -962,7 +967,7 @@ class LayerIndexLayerSource(LayerSource): transaction.set_autocommit(True) # update recipes; paginate by layer version / layer branch - print "Fetching target information" + logger.debug("Fetching target information") recipes_info = _get_json_response(apilinks['recipes'] + "?filter=layerbranch:%s" % "OR".join(map(lambda x: str(x.up_id), Layer_Version.objects.filter(layer_source = self))) )
@@ -1236,8 +1241,7 @@ def invalidate_cache(**kwargs): try: cache.clear() except Exception as e: - print "Problem with cache backend: Failed to clear cache" - pass + logger.warning("Problem with cache backend: Failed to clear cache: %s" % e) django.db.models.signals.post_save.connect(invalidate_cache) django.db.models.signals.post_delete.connect(invalidate_cache)
lib/toaster/toastergui/views.py +4 -0
@@ -42,6 +42,10 @@ import json from os.path import dirname import itertools +import logging + +logger = logging.getLogger("toaster") + # all new sessions should come through the landing page; # determine in which mode we are running in, and redirect appropriately def landing(request):
Signature · 1 line
2.1.4
PATCH 7/14 bitbake: toastergui: rename variables in views.py
In reply to Michael WoodFrom: Alexandru DAMIAN <[email protected]>
Renaming various variables in the views.py file to make the code more readable, and prevent overshadowing of global variables with local variables that have a different name.
--- lib/toaster/toastergui/views.py | 116 ++++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 53 deletions(-)
lib/toaster/toastergui/views.py +63 -53
@@ -284,30 +284,30 @@ def _get_toggle_order_icon(request, orderkey): return None # we check that the input comes in a valid form that we can recognize -def _validate_input(input, model): +def _validate_input(field_input, model): invalid = None - if input: - input_list = input.split(FIELD_SEPARATOR) + if field_input: + field_input_list = field_input.split(FIELD_SEPARATOR) # Check we have only one colon - if len(input_list) != 2: - invalid = "We have an invalid number of separators: " + input + " -> " + str(input_list) + if len(field_input_list) != 2: + invalid = "We have an invalid number of separators: " + field_input + " -> " + str(field_input_list) return None, invalid # Check we have an equal number of terms both sides of the colon - if len(input_list[0].split(AND_VALUE_SEPARATOR)) != len(input_list[1].split(AND_VALUE_SEPARATOR)): + if len(field_input_list[0].split(AND_VALUE_SEPARATOR)) != len(field_input_list[1].split(AND_VALUE_SEPARATOR)): invalid = "Not all arg names got values" - return None, invalid + str(input_list) + return None, invalid + str(field_input_list) # Check we are looking for a valid field valid_fields = model._meta.get_all_field_names() - for field in input_list[0].split(AND_VALUE_SEPARATOR): - if not reduce(lambda x, y: x or y, map(lambda x: field.startswith(x), [ x for x in valid_fields ])): + for field in field_input_list[0].split(AND_VALUE_SEPARATOR): + if not reduce(lambda x, y: x or y, [ field.startswith(x) for x in valid_fields ]): return None, (field, [ x for x in valid_fields ]) - return input, invalid + return field_input, invalid # uses search_allowed_fields in orm/models.py to create a search query # for these fields with the supplied input text
@@ -542,23 +542,23 @@ def generateCoveredList2( revlist = None ): def task( request, build_id, task_id ): template = "task.html" - tasks = Task.objects.filter( pk=task_id ) - if tasks.count( ) == 0: + tasks_list = Task.objects.filter( pk=task_id ) + if tasks_list.count( ) == 0: return redirect( builds ) - task = tasks[ 0 ]; + task_object = tasks_list[ 0 ]; dependencies = sorted( - _find_task_dep( task ), + _find_task_dep( task_object ), key=lambda t:'%s_%s %s'%(t.recipe.name, t.recipe.version, t.task_name)) reverse_dependencies = sorted( - _find_task_revdep( task ), + _find_task_revdep( task_object ), key=lambda t:'%s_%s %s'%( t.recipe.name, t.recipe.version, t.task_name )) coveredBy = ''; - if ( task.outcome == Task.OUTCOME_COVERED ): + if ( task_object.outcome == Task.OUTCOME_COVERED ): # _list = generateCoveredList( task ) - coveredBy = sorted(generateCoveredList2( _find_task_revdep( task ) ), key = lambda x: x.recipe.name) + coveredBy = sorted(generateCoveredList2( _find_task_revdep( task_object ) ), key = lambda x: x.recipe.name) log_head = '' log_body = '' - if task.outcome == task.OUTCOME_FAILED: + if task_object.outcome == task_object.OUTCOME_FAILED: pass uri_list= [ ]
@@ -570,12 +570,13 @@ def task( request, build_id, task_id ): if (v.count() > 0): for mirror in v[0].variable_value.split('\\n'): s=re.sub('.* ','',mirror.strip(' \t\n\r')) - if len(s): uri_list.append(s) + if len(s): + uri_list.append(s) context = { 'build' : Build.objects.filter( pk = build_id )[ 0 ], - 'object' : task, - 'task' : task, + 'object' : task_object, + 'task' : task_object, 'covered_by' : coveredBy, 'deps' : dependencies, 'rdeps' : reverse_dependencies,
@@ -587,8 +588,8 @@ def task( request, build_id, task_id ): if request.GET.get( 'show_matches', "" ): context[ 'showing_matches' ] = True context[ 'matching_tasks' ] = Task.objects.filter( - sstate_checksum=task.sstate_checksum ).filter( - build__completed_on__lt=task.build.completed_on).exclude( + sstate_checksum=task_object.sstate_checksum ).filter( + build__completed_on__lt=task_object.build.completed_on).exclude( order__isnull=True).exclude(outcome=Task.OUTCOME_NA).order_by('-build__completed_on') return render( request, template, context )
@@ -598,10 +599,10 @@ def recipe(request, build_id, recipe_id, active_tab="1"): if Recipe.objects.filter(pk=recipe_id).count() == 0 : return redirect(builds) - object = Recipe.objects.get(pk=recipe_id) - layer_version = Layer_Version.objects.get(pk=object.layer_version_id) + recipe_object = Recipe.objects.get(pk=recipe_id) + layer_version = Layer_Version.objects.get(pk=recipe_object.layer_version_id) layer = Layer.objects.get(pk=layer_version.layer_id) - tasks = 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) + tasks_list = Task.objects.filter(recipe_id = recipe_id, build_id = build_id).exclude(order__isnull=True).exclude(task_name__endswith='_setscene').exclude(outcome=Task.OUTCOME_NA) package_count = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0).count() if active_tab != '1' and active_tab != '3' and active_tab != '4' :
@@ -611,10 +612,10 @@ def recipe(request, build_id, recipe_id, active_tab="1"): context = { 'build' : Build.objects.get(pk=build_id), - 'object' : object, + 'object' : recipe_object, 'layer_version' : layer_version, 'layer' : layer, - 'tasks' : tasks, + 'tasks' : tasks_list, 'package_count' : package_count, 'tab_states' : tab_states, }
@@ -632,7 +633,7 @@ def recipe_packages(request, build_id, recipe_id): return _redirect_parameters( 'recipe_packages', request.GET, mandatory_parameters, build_id = build_id, recipe_id = recipe_id) (filter_string, search_term, ordering_string) = _search_tuple(request, Package) - recipe = Recipe.objects.get(pk=recipe_id) + recipe_object = Recipe.objects.get(pk=recipe_id) queryset = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0) package_count = queryset.count() queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name')
@@ -641,7 +642,7 @@ def recipe_packages(request, build_id, recipe_id): context = { 'build' : Build.objects.get(pk=build_id), - 'recipe' : recipe, + 'recipe' : recipe_object, 'objects' : packages, 'object_count' : package_count, 'tablecols':[
@@ -952,13 +953,13 @@ def dirinfo(request, build_id, target_id, file_path=None): } return render(request, template, context) -def _find_task_dep(task): - return map(lambda x: x.depends_on, Task_Dependency.objects.filter(task=task).filter(depends_on__order__gt = 0).exclude(depends_on__outcome = Task.OUTCOME_NA).select_related("depends_on")) +def _find_task_dep(task_object): + return map(lambda x: x.depends_on, Task_Dependency.objects.filter(task=task_object).filter(depends_on__order__gt = 0).exclude(depends_on__outcome = Task.OUTCOME_NA).select_related("depends_on")) -def _find_task_revdep(task): +def _find_task_revdep(task_object): tp = [] - tp = map(lambda t: t.task, Task_Dependency.objects.filter(depends_on=task).filter(task__order__gt=0).exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")) + tp = map(lambda t: t.task, Task_Dependency.objects.filter(depends_on=task_object).filter(task__order__gt=0).exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")) return tp def _find_task_revdep_list(tasklist):
@@ -966,8 +967,8 @@ def _find_task_revdep_list(tasklist): tp = map(lambda t: t.task, Task_Dependency.objects.filter(depends_on__in=tasklist).filter(task__order__gt=0).exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")) return tp -def _find_task_provider(task): - task_revdeps = _find_task_revdep(task) +def _find_task_provider(task_object): + task_revdeps = _find_task_revdep(task_object) for tr in task_revdeps: if tr.outcome != Task.OUTCOME_COVERED: return tr
@@ -1055,15 +1056,15 @@ def tasks_common(request, build_id, variant, task_anchor): i=0 a=int(anchor) count_per_page=int(pagesize) - for task in queryset.iterator(): - if a == task.order: + for task_object in queryset.iterator(): + if a == task_object.order: new_page= (i / count_per_page ) + 1 request.GET.__setitem__('page', new_page) mandatory_parameters['page']=new_page return _redirect_parameters( variant, request.GET, mandatory_parameters, build_id = build_id) i += 1 - tasks = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1)) + task_objects = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1)) # define (and modify by variants) the 'tablecols' members tc_order={
@@ -1073,7 +1074,10 @@ def tasks_common(request, build_id, variant, task_anchor): 'orderkey' : 'order', 'orderfield':_get_toggle_order(request, "order"), 'ordericon':_get_toggle_order_icon(request, "order")} - if 'tasks' == variant: tc_order['hidden']='0'; del tc_order['clclass'] + if 'tasks' == variant: + tc_order['hidden']='0' + del tc_order['clclass'] + tc_recipe={ 'name':'Recipe', 'qhelp':'The name of the recipe to which each task applies',
@@ -1131,14 +1135,7 @@ def tasks_common(request, build_id, variant, task_anchor): } } - tc_log={ - 'name':'Log', - 'qhelp':'Path to the task log file', - 'orderfield': _get_toggle_order(request, "logfile"), - 'ordericon':_get_toggle_order_icon(request, "logfile"), - 'orderkey' : 'logfile', - 'clclass': 'task_log', 'hidden' : 1, - } + tc_cache={ 'name':'Cache attempt', 'qhelp':'This column tells you if a task tried to restore output from the <code>sstate-cache</code> directory or mirrors, and reports the result: Succeeded, Failed or File not in cache',
@@ -1167,7 +1164,11 @@ def tasks_common(request, build_id, variant, task_anchor): 'orderkey' : 'elapsed_time', 'clclass': 'time_taken', 'hidden' : 1, } - if 'buildtime' == variant: tc_time['hidden']='0'; del tc_time['clclass']; tc_cache['hidden']='1'; + if 'buildtime' == variant: + tc_time['hidden']='0' + del tc_time['clclass'] + tc_cache['hidden']='1' + tc_cpu={ 'name':'CPU usage', 'qhelp':'The percentage of task CPU utilization',
@@ -1176,7 +1177,12 @@ def tasks_common(request, build_id, variant, task_anchor): 'orderkey' : 'cpu_usage', 'clclass': 'cpu_used', 'hidden' : 1, } - if 'cpuusage' == variant: tc_cpu['hidden']='0'; del tc_cpu['clclass']; tc_cache['hidden']='1'; + + if 'cpuusage' == variant: + tc_cpu['hidden']='0' + del tc_cpu['clclass'] + tc_cache['hidden']='1' + tc_diskio={ 'name':'Disk I/O (ms)', 'qhelp':'Number of miliseconds the task spent doing disk input and output',
@@ -1185,7 +1191,10 @@ def tasks_common(request, build_id, variant, task_anchor): 'orderkey' : 'disk_io', 'clclass': 'disk_io', 'hidden' : 1, } - if 'diskio' == variant: tc_diskio['hidden']='0'; del tc_diskio['clclass']; tc_cache['hidden']='1'; + if 'diskio' == variant: + tc_diskio['hidden']='0' + del tc_diskio['clclass'] + tc_cache['hidden']='1' build = Build.objects.get(pk=build_id)
@@ -1194,7 +1203,7 @@ def tasks_common(request, build_id, variant, task_anchor): 'filter_search_display': filter_search_display, 'title': title_variant, 'build': build, - 'objects': tasks, + 'objects': task_objects, 'default_orderby' : orderby, 'search_term': search_term, 'total_count': queryset_with_search.count(),
@@ -1246,7 +1255,8 @@ def recipes(request, build_id): recipes = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1)) # prefetch the forward and reverse recipe dependencies - deps = { }; revs = { } + deps = { } + revs = { } queryset_dependency=Recipe_Dependency.objects.filter(recipe__layer_version__build_id = build_id).select_related("depends_on", "recipe") for recipe in recipes: deplist = [ ]
Signature · 1 line
2.1.4
PATCH 8/14 bitbake: toaster: do not stop data import on bad data
In reply to Michael WoodFrom: Alexandru DAMIAN <[email protected]>
The layer data update in Layer source needlessly stops processing by throwing an Exception when bad layer data cannot be inserted in the database (e.g. invalid dependencies).
This patch changes the behaviour to ignore bad data, and continue with data import after detailed logging through the logging system.
[YOCTO #7955]
--- lib/toaster/orm/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
lib/toaster/orm/models.py +4 -4
@@ -19,7 +19,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from django.db import models +from django.db import models, IntegrityError from django.db.models import F, Q, Avg, Max from django.utils import timezone
@@ -938,7 +938,7 @@ class LayerIndexLayerSource(LayerSource): try: dependlist[lv].append(Layer_Version.objects.get(layer_source = self, layer__up_id = ldi['dependency'], up_branch = lv.up_branch)) except Layer_Version.DoesNotExist: - logger.warning("Cannot find layer version %s dep:%s up_brach:%s" % (self, ldi['dependency'], lv.up_branch)) + logger.warning("Cannot find layer version (ls:%s), up_id:%s lv:%s" % (self, ldi['dependency'], lv)) for lv in dependlist: LayerVersionDependency.objects.filter(layer_version = lv).delete()
@@ -989,8 +989,8 @@ class LayerIndexLayerSource(LayerSource): if 'inherits' in ri: ro.is_image = 'image' in ri['inherits'].split() ro.save() - except Exception: - logger.warning("Duplicate Recipe, ignoring: %s " % vars(ro)) + except IntegrityError as e: + logger.debug("Failed saving recipe, ignoring: %s (%s:%s)" % (e, ro.layer_version, ri['filepath']+"/"+ri['filename'])) if not connection.features.autocommits_when_autocommit_is_off: transaction.set_autocommit(True)
Signature · 1 line
2.1.4
PATCH 9/14 bitbake: toaster: replace raising Exceptions in loadconf
In reply to Michael WoodFrom: Alexandru DAMIAN <[email protected]>
This patch replaces throwing Exceptions in toaster loadconf command with proper explicit error messages. This allows the user to properly understand and debug what went wrong when loading a config file.
Additionally we change a bit the logic around auto-detecting relative giturl handling so the user gets proper error messages when trying to load an invalid toasterconf.json file.
[YOCTO #7945]
--- .../bldcontrol/management/commands/loadconf.py | 37 ++++++++++++++-------- 1 file changed, 23 insertions(+), 14 deletions(-)
lib/toaster/bldcontrol/management/commands/loadconf.py +23 -14
@@ -1,10 +1,14 @@ from django.core.management.base import BaseCommand, CommandError from orm.models import LayerSource, ToasterSetting, Branch, Layer, Layer_Version from orm.models import BitbakeVersion, Release, ReleaseDefaultLayer, ReleaseLayerSourcePriority +from django.db import IntegrityError import os from checksettings import DN +import logging +logger = logging.getLogger("toaster") + def _reduce_canon_path(path): components = [] for c in path.split("/"):
@@ -34,7 +38,7 @@ class Command(BaseCommand): if not os.path.exists(filepath) or not os.path.isfile(filepath): raise Exception("Failed to find toaster config file %s ." % filepath) - import json, pprint + import json data = json.loads(open(filepath, "r").read()) # verify config file validity before updating settings
@@ -49,7 +53,7 @@ class Command(BaseCommand): cmd = subprocess.Popen("git remote -v", shell=True, cwd = os.path.dirname(filepath), stdout=subprocess.PIPE, stderr = subprocess.PIPE) (out,err) = cmd.communicate() if cmd.returncode != 0: - raise Exception("Error while importing layer vcs_url: git error: %s" % err) + logging.warning("Error while importing layer vcs_url: git error: %s" % err) for line in out.split("\n"): try: (name, path) = line.split("\t", 1)
@@ -59,16 +63,20 @@ class Command(BaseCommand): except ValueError: pass if url == None: - raise Exception("Error while looking for remote \"%s\" in \"%s\"" % (remote_name, out)) + logging.warning("Error while looking for remote \"%s\" in \"%s\"" % (remote_name, out)) return url # import bitbake data for bvi in data['bitbake']: bvo, created = BitbakeVersion.objects.get_or_create(name=bvi['name']) - bvo.giturl = bvi['giturl'] if bvi['giturl'].startswith("remote:"): bvo.giturl = _read_git_url_from_local_repository(bvi['giturl']) + if bvo.giturl is None: + logger.error("The toaster config file references the local git repo, but Toaster cannot detect it.\nYour local configuration for bitbake version %s is invalid. Make sure that the toasterconf.json file is correct." % bvi['name']) + + if bvo.giturl is None: + bvo.giturl = bvi['giturl'] bvo.branch = bvi['branch'] bvo.dirpath = bvi['dirpath'] bvo.save()
@@ -89,13 +97,12 @@ class Command(BaseCommand): assert ((_get_id_for_sourcetype(lsi['sourcetype']) == LayerSource.TYPE_LAYERINDEX) or apiurl.startswith("/")), (lsi['sourcetype'],apiurl) try: - ls = LayerSource.objects.get(sourcetype = _get_id_for_sourcetype(lsi['sourcetype']), apiurl = apiurl) - except LayerSource.DoesNotExist: - ls = LayerSource.objects.create( - name = lsi['name'], - sourcetype = _get_id_for_sourcetype(lsi['sourcetype']), - apiurl = apiurl - ) + ls, created = LayerSource.objects.get_or_create(sourcetype = _get_id_for_sourcetype(lsi['sourcetype']), apiurl = apiurl) + ls.name = lsi['name'] + ls.save() + except IntegrityError as e: + logger.warning("IntegrityError %s \nWhile setting name %s for layer source %s " % (e, lsi['name'], ls)) + layerbranches = [] for branchname in lsi['branches']:
@@ -111,12 +118,14 @@ class Command(BaseCommand): lo.local_path = _reduce_canon_path(os.path.join(ls.apiurl, layerinfo['local_path'])) if not os.path.exists(lo.local_path): - raise Exception("Local layer path %s must exists." % lo.local_path) + logger.error("Local layer path %s must exists. Are you trying to import a layer that does not exist ? Check your local toasterconf.json" % lo.local_path) - lo.vcs_url = layerinfo['vcs_url'] if layerinfo['vcs_url'].startswith("remote:"): lo.vcs_url = _read_git_url_from_local_repository(layerinfo['vcs_url']) - else: + if lo.vcs_url is None: + logger.error("The toaster config file references the local git repo, but Toaster cannot detect it.\nYour local configuration for layer %s is invalid. Make sure that the toasterconf.json file is correct." % layerinfo['name']) + + if lo.vcs_url is None: lo.vcs_url = layerinfo['vcs_url'] if 'layer_index_url' in layerinfo:
Signature · 1 line
2.1.4
PATCH 10/14 bitbake: toaster: fix updates on failed build requests
In reply to Michael WoodFrom: Alexandru DAMIAN <[email protected]>
The patch to fix the original mistake is wrong, in the sense that a constant from the BuildRequest class is used on the build object.
Fixing the patch to bring in the correct constant in.
--- lib/toaster/bldcontrol/management/commands/runbuilds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
lib/toaster/bldcontrol/management/commands/runbuilds.py +1 -1
@@ -130,7 +130,7 @@ class Command(NoArgsCommand): # update all BuildRequests without a build created for br in BuildRequest.objects.filter(build = None): br.build = Build.objects.create(project = br.project, completed_on = br.updated, started_on = br.created) - br.build.outcome = BuildRequest.REQ_FAILED + br.build.outcome = Build.FAILED try: br.build.machine = br.brvariable_set.get(name='MACHINE').value except BRVariable.DoesNotExist:
Signature · 1 line
2.1.4
PATCH 11/14 bitbake: toaster: fix pylint errors
In reply to Michael WoodFrom: Alexandru Damian <[email protected]>
Prompted by issues discovered during running pylint on the toaster sources, this patch fixes:
* missing import in toaster ui
* improper call of function in toaster ui (logger.debug)
* improper function definitions in bbcontroller
* invalid references to objects in bldcontrol.models
* proper initialization of object fields in ToasterTables
Also inhibiting specific pylint errors in files where the problems are mis-identified.
--- lib/bb/ui/toasterui.py | 5 +++-- lib/toaster/bldcontrol/bbcontroller.py | 2 +- lib/toaster/orm/models.py | 2 +- lib/toaster/toastergui/views.py | 4 ++++ lib/toaster/toastergui/widgets.py | 3 +++ lib/toaster/toastermain/management/commands/perf.py | 7 ++++++- 6 files changed, 18 insertions(+), 5 deletions(-)
lib/bb/ui/toasterui.py +3 -2
@@ -21,6 +21,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import division +import sys try: import bb except RuntimeError as exc:
@@ -255,10 +256,10 @@ def main(server, eventHandler, params ): # we start a new build info if buildinfohelper.brbe is not None: - logger.debug(1, "ToasterUI under BuildEnvironment management - exiting after the build") + logger.debug("ToasterUI under BuildEnvironment management - exiting after the build") server.terminateServer() else: - logger.debug(1, "ToasterUI prepared for new build") + logger.debug("ToasterUI prepared for new build") errors = 0 warnings = 0 taskfailures = []
lib/toaster/bldcontrol/bbcontroller.py +1 -1
@@ -177,7 +177,7 @@ class BuildEnvironmentController(object): return BitbakeController(self.connection) - def getArtifact(path): + def getArtifact(self, path): """ This call returns an artifact identified by the 'path'. How 'path' is interpreted as up to the implementing BEC. The return MUST be a REST URL where a GET will actually return the content of the artifact, e.g. for use as a "download link" in a web UI.
lib/toaster/orm/models.py +1 -1
@@ -81,7 +81,7 @@ class ProjectManager(models.Manager): for rdl in release.releasedefaultlayer_set.all(): try: - lv =Layer_Version.objects.filter(layer__name = rdl.layer_name, up_branch__name = release.branch_name)[0].get_equivalents_wpriority(prj)[0] + lv = Layer_Version.objects.filter(layer__name = rdl.layer_name, up_branch__name = release.branch_name)[0].get_equivalents_wpriority(prj)[0] ProjectLayer.objects.create( project = prj, layercommit = lv, optional = False )
lib/toaster/toastergui/views.py +4 -0
@@ -19,6 +19,10 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# pylint: disable=method-hidden +# Gives E:848, 4: An attribute defined in json.encoder line 162 hides this method (method-hidden) +# which is an invalid warning + import operator,re from django.db.models import F, Q, Sum, Count, Max
lib/toaster/toastergui/widgets.py +3 -0
@@ -334,6 +334,9 @@ class ToasterTemplateView(TemplateView): # renders a instance in a template, or returns the context as json # the class-equivalent of the _template_renderer decorator for views + def __init__(self, *args, **kwargs): + super(ToasterTemplateView, self).__init__(*args, **kwargs) + self.context_entries = [] def get(self, *args, **kwargs): if self.request.GET.get('format', None) == 'json':
lib/toaster/toastermain/management/commands/perf.py +6 -1
@@ -2,7 +2,12 @@ from django.core.management.base import BaseCommand from django.test.client import Client import os, sys, re import requests -import toastermain.settings as settings +from django.conf import settings + +# pylint: disable=E1103 +# Instance of 'WSGIRequest' has no 'status_code' member +# (but some types could not be inferred) (maybe-no-member) + class Command(BaseCommand): help = "Test the response time for all toaster urls"
Signature · 1 line
2.1.4
PATCH 12/14 bitbake: toaster logger: fix pylint issues
In reply to Michael WoodFrom: Alexandru DAMIAN <[email protected]>
This patch fixes pylint issues in the toaster build data logger, toasterui. The following types of warnings are touched here:
* fixing imports
* unused variables are set to _
* logger calls now use lazy evaluation instead of formatting the string
* correct whitespace identation
* removes unneeded "pass" statements, and extra parantheses
* disable specific pylint warnings when decideing to override them
--- lib/bb/ui/buildinfohelper.py | 130 +++++++++++++++++++++++-------------------- lib/bb/ui/toasterui.py | 57 +++++++++---------- 2 files changed, 98 insertions(+), 89 deletions(-)
lib/bb/ui/buildinfohelper.py +71 -59
@@ -19,12 +19,20 @@ import sys import bb import re -import ast +import os os.environ["DJANGO_SETTINGS_MODULE"] = "toaster.toastermain.settings" + from django.utils import timezone -import toaster.toastermain.settings as toaster_django_settings + + +def _configure_toaster(): + """ Add toaster to sys path for importing modules + """ + sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'toaster')) +_configure_toaster() + from toaster.orm.models import Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage, HelpText from toaster.orm.models import Target_Image_File, BuildArtifact from toaster.orm.models import Variable, VariableHistory
@@ -33,14 +41,17 @@ from toaster.orm.models import Task_Dependency, Package_Dependency from toaster.orm.models import Recipe_Dependency from toaster.orm.models import Project +from bldcontrol.models import BuildEnvironment, BuildRequest -from bb.msg import BBLogFormatter as format +from bb.msg import BBLogFormatter as formatter from django.db import models from pprint import pformat import logging from django.db import transaction, connection +# pylint: disable=invalid-name +# the logger name is standard throughout BitBake logger = logging.getLogger("ToasterLogger")
@@ -57,7 +68,6 @@ class ORMWrapper(object): self.layer_version_objects = [] self.task_objects = {} self.recipe_objects = {} - pass @staticmethod def _build_key(**kwargs):
@@ -106,6 +116,13 @@ class ORMWrapper(object): return vars(self)[dictname][key] + # pylint: disable=no-self-use + # we disable detection of no self use in functions because the methods actually work on the object + # even if they don't touch self anywhere + + # pylint: disable=bad-continuation + # we do not follow the python conventions for continuation indentation due to long lines here + def create_build_object(self, build_info, brbe, project_id): assert 'machine' in build_info assert 'distro' in build_info
@@ -119,8 +136,7 @@ class ORMWrapper(object): buildrequest = None if brbe is not None: # this build was triggered by a request from a user logger.debug(1, "buildinfohelper: brbe is %s" % brbe) - from bldcontrol.models import BuildEnvironment, BuildRequest - br, be = brbe.split(":") + br, _ = brbe.split(":") buildrequest = BuildRequest.objects.get(pk = br) prj = buildrequest.project
@@ -129,18 +145,16 @@ class ORMWrapper(object): prj = Project.objects.get(pk = project_id) else: # this build was triggered by a legacy system, or command line interactive mode - prj, created = Project.objects.get_or_create(pk=0, name="Default Project") + prj, _ = Project.objects.get_or_create(pk=0, name="Default Project") logger.debug(1, "buildinfohelper: project is not specified, defaulting to %s" % prj) if buildrequest is not None: build = buildrequest.build - logger.info("Updating existing build, with %s" % build_info) + logger.info("Updating existing build, with %s", build_info) build.machine=build_info['machine'] build.distro=build_info['distro'] build.distro_version=build_info['distro_version'] - started_on=build_info['started_on'] - completed_on=build_info['started_on'] build.cooker_log_path=build_info['cooker_log_path'] build.build_name=build_info['build_name'] build.bitbake_version=build_info['bitbake_version']
@@ -210,8 +224,8 @@ class ORMWrapper(object): task_name=task_information['task_name'] ) if created and must_exist: - task_information['debug'] = "build id %d, recipe id %d" % (task_information['build'].pk, task_information['recipe'].pk) - raise NotExisting("Task object created when expected to exist", task_information) + task_information['debug'] = "build id %d, recipe id %d" % (task_information['build'].pk, task_information['recipe'].pk) + raise NotExisting("Task object created when expected to exist", task_information) object_changed = False for v in vars(task_object):
@@ -278,7 +292,7 @@ class ORMWrapper(object): assert 'priority' in layer_version_information assert 'local_path' in layer_version_information - layer_version_object, created = Layer_Version.objects.get_or_create( + layer_version_object, _ = Layer_Version.objects.get_or_create( build = build_obj, layer = layer_obj, branch = layer_version_information['branch'],
@@ -296,13 +310,12 @@ class ORMWrapper(object): assert 'layer_index_url' in layer_information if brbe is None: - layer_object, created = Layer.objects.get_or_create( + layer_object, _ = Layer.objects.get_or_create( name=layer_information['name'], layer_index_url=layer_information['layer_index_url']) return layer_object else: # we are under managed mode; we must match the layer used in the Project Layer - from bldcontrol.models import BuildEnvironment, BuildRequest br_id, be_id = brbe.split(":") # find layer by checkout path;
@@ -422,7 +435,7 @@ class ORMWrapper(object): try: filetarget_obj = Target_File.objects.get(target = target_obj, path = filetarget_path) - except Exception as e: + except Target_File.DoesNotExist: # we might have an invalid link; no way to detect this. just set it to None filetarget_obj = None
@@ -498,11 +511,11 @@ class ORMWrapper(object): if len(packagedeps_objs) > 0: Package_Dependency.objects.bulk_create(packagedeps_objs) - if (len(errormsg) > 0): - logger.warn("buildinfohelper: target_package_info could not identify recipes: \n%s" % errormsg) + if len(errormsg) > 0: + logger.warn("buildinfohelper: target_package_info could not identify recipes: \n%s", errormsg) def save_target_image_file_information(self, target_obj, file_name, file_size): - target_image_file = Target_Image_File.objects.create( target = target_obj, + Target_Image_File.objects.create( target = target_obj, file_name = file_name, file_size = file_size)
@@ -542,7 +555,7 @@ class ORMWrapper(object): if 'OPKGN' in package_info.keys(): pname = package_info['OPKGN'] - bp_object, created = Package.objects.get_or_create( build = build_obj, + bp_object, _ = Package.objects.get_or_create( build = build_obj, name = pname ) bp_object.installed_name = package_info['PKG']
@@ -559,7 +572,7 @@ class ORMWrapper(object): # save any attached file information packagefile_objects = [] for path in package_info['FILES_INFO']: - packagefile_objects.append(Package_File( package = bp_object, + packagefile_objects.append(Package_File( package = bp_object, path = path, size = package_info['FILES_INFO'][path] )) if len(packagefile_objects):
@@ -643,7 +656,19 @@ class ORMWrapper(object): HelpText.objects.bulk_create(helptext_objects) -class MockEvent: pass # sometimes we mock an event, declare it here + +class MockEvent(object): + """ This object is used to create event, for which normal event-processing methods can + be used, out of data that is not coming via an actual event + """ + def __init__(self): + self.msg = None + self.levelno = None + self.taskname = None + self.taskhash = None + self.pathname = None + self.lineno = None + class BuildInfoHelper(object): """ This class gathers the build information from the server and sends it
@@ -652,9 +677,12 @@ class BuildInfoHelper(object): Keeps in memory all data that needs matching before writing it to the database """ + # pylint: disable=protected-access + # the code will look into the protected variables of the event; no easy way around this + # pylint: disable=bad-continuation + # we do not follow the python conventions for continuation indentation due to long lines here def __init__(self, server, has_build_history = False): - self._configure_django() self.internal_state = {} self.internal_state['taskdata'] = {} self.task_order = 0
@@ -671,10 +699,6 @@ class BuildInfoHelper(object): logger.debug(1, "buildinfohelper: Build info helper inited %s" % vars(self)) - def _configure_django(self): - # Add toaster to sys path for importing modules - sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'toaster')) - ################### ## methods to convert event/external info into objects that the ORM layer uses
@@ -727,7 +751,6 @@ class BuildInfoHelper(object): else: br_id, be_id = self.brbe.split(":") from bldcontrol.bbcontroller import getBuildEnvironmentController - from bldcontrol.models import BuildRequest bc = getBuildEnvironmentController(pk = be_id) def _slkey_managed(layer_version):
@@ -747,11 +770,11 @@ class BuildInfoHelper(object): return lvo #if we get here, we didn't read layers correctly; dump whatever information we have on the error log - logger.warn("Could not match layer version for recipe path %s : %s" % (path, self.orm_wrapper.layer_version_objects)) + logger.warn("Could not match layer version for recipe path %s : %s", path, self.orm_wrapper.layer_version_objects) #mockup the new layer - unknown_layer, created = Layer.objects.get_or_create(name="__FIXME__unidentified_layer", layer_index_url="") - unknown_layer_version_obj, created = Layer_Version.objects.get_or_create(layer = unknown_layer, build = self.internal_state['build']) + unknown_layer, _ = Layer.objects.get_or_create(name="__FIXME__unidentified_layer", layer_index_url="") + unknown_layer_version_obj, _ = Layer_Version.objects.get_or_create(layer = unknown_layer, build = self.internal_state['build']) # append it so we don't run into this error again and again self.orm_wrapper.layer_version_objects.append(unknown_layer_version_obj)
@@ -798,13 +821,6 @@ class BuildInfoHelper(object): return build_stats_path - def _remove_redundant(self, string): - ret = [] - for i in string.split(): - if i not in ret: - ret.append(i) - return " ".join(sorted(ret)) - ################################ ## external available methods to store information
@@ -827,7 +843,7 @@ class BuildInfoHelper(object): self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)] = layerinfos[layer]['version'] self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)]['local_path'] = layerinfos[layer]['local_path'] except NotExisting as nee: - logger.warn("buildinfohelper: cannot identify layer exception:%s " % nee) + logger.warn("buildinfohelper: cannot identify layer exception:%s ", nee) def store_started_build(self, event):
@@ -861,8 +877,7 @@ class BuildInfoHelper(object): path_prefixes = [] if self.brbe is not None: - br_id, be_id = self.brbe.split(":") - from bldcontrol.models import BuildEnvironment, BuildRequest + _, be_id = self.brbe.split(":") be = BuildEnvironment.objects.get(pk = be_id) path_prefixes.append(be.builddir)
@@ -887,7 +902,6 @@ class BuildInfoHelper(object): def update_target_image_file(self, event): - image_fstypes = self.server.runCommand(["getVariable", "IMAGE_FSTYPES"])[0] evdata = BuildInfoHelper._get_data_from_event(event) for t in self.internal_state['targets']:
@@ -949,7 +963,7 @@ class BuildInfoHelper(object): self.task_order += 1 task_information['order'] = self.task_order - task_obj = self.orm_wrapper.get_update_task_object(task_information) + self.orm_wrapper.get_update_task_object(task_information) self.internal_state['taskdata'][identifier] = { 'outcome': task_information['outcome'],
@@ -970,7 +984,7 @@ class BuildInfoHelper(object): file_path__endswith = recipe_information['file_path'], name = recipename) except Recipe.DoesNotExist: - logger.error("Could not find recipe for recipe_information %s name %s" % (pformat(recipe_information), recipename)) + logger.error("Could not find recipe for recipe_information %s name %s" , pformat(recipe_information), recipename) raise task_information = {}
@@ -981,7 +995,7 @@ class BuildInfoHelper(object): task_information['disk_io'] = taskstats['disk_io'] if 'elapsed_time' in taskstats: task_information['elapsed_time'] = taskstats['elapsed_time'] - task_obj = self.orm_wrapper.get_update_task_object(task_information, True) # must exist + self.orm_wrapper.get_update_task_object(task_information, True) # must exist def update_and_store_task(self, event): assert 'taskfile' in vars(event)
@@ -1047,7 +1061,7 @@ class BuildInfoHelper(object): def store_missed_state_tasks(self, event): for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['missed']: - identifier = fn + taskname + "_setscene" + # identifier = fn + taskname + "_setscene" recipe_information = self._get_recipe_information_from_taskfile(fn) recipe = self.orm_wrapper.get_update_recipe_object(recipe_information) mevent = MockEvent()
@@ -1065,7 +1079,7 @@ class BuildInfoHelper(object): for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['found']: - identifier = fn + taskname + "_setscene" + # identifier = fn + taskname + "_setscene" recipe_information = self._get_recipe_information_from_taskfile(fn) recipe = self.orm_wrapper.get_update_recipe_object(recipe_information) mevent = MockEvent()
@@ -1105,7 +1119,7 @@ class BuildInfoHelper(object): # save layer version priorities if 'layer-priorities' in event._depgraph.keys(): for lv in event._depgraph['layer-priorities']: - (name, path, regexp, priority) = lv + (_, path, _, priority) = lv layer_version_obj = self._get_layer_version_for_path(path[1:]) # paths start with a ^ assert layer_version_obj is not None layer_version_obj.priority = priority
@@ -1218,8 +1232,8 @@ class BuildInfoHelper(object): taskdeps_objects.append(Task_Dependency( task = target, depends_on = dep )) Task_Dependency.objects.bulk_create(taskdeps_objects) - if (len(errormsg) > 0): - logger.warn("buildinfohelper: dependency info not identify recipes: \n%s" % errormsg) + if len(errormsg) > 0: + logger.warn("buildinfohelper: dependency info not identify recipes: \n%s", errormsg) def store_build_package_information(self, event):
@@ -1232,7 +1246,6 @@ class BuildInfoHelper(object): def _store_build_done(self, errorcode): logger.info("Build exited with errorcode %d", errorcode) br_id, be_id = self.brbe.split(":") - from bldcontrol.models import BuildEnvironment, BuildRequest be = BuildEnvironment.objects.get(pk = be_id) be.lock = BuildEnvironment.LOCK_LOCK be.save()
@@ -1247,7 +1260,7 @@ class BuildInfoHelper(object): def store_log_error(self, text): mockevent = MockEvent() - mockevent.levelno = format.ERROR + mockevent.levelno = formatter.ERROR mockevent.msg = text mockevent.pathname = '-- None' mockevent.lineno = LogMessage.ERROR
@@ -1263,7 +1276,7 @@ class BuildInfoHelper(object): def store_log_event(self, event): - if event.levelno < format.WARNING: + if event.levelno < formatter.WARNING: return if 'args' in vars(event):
@@ -1276,8 +1289,7 @@ class BuildInfoHelper(object): self.internal_state['backlog'].append(event) return else: # we're under Toaster control, the build is already created - from bldcontrol.models import BuildRequest, BRError - br, be = self.brbe.split(":") + br, _ = self.brbe.split(":") buildrequest = BuildRequest.objects.get(pk = br) self.internal_state['build'] = buildrequest.build
@@ -1293,9 +1305,9 @@ class BuildInfoHelper(object): log_information = {} log_information['build'] = self.internal_state['build'] - if event.levelno == format.ERROR: + if event.levelno == formatter.ERROR: log_information['level'] = LogMessage.ERROR - elif event.levelno == format.WARNING: + elif event.levelno == formatter.WARNING: log_information['level'] = LogMessage.WARNING elif event.levelno == -2: # toaster self-logging log_information['level'] = -2
@@ -1305,7 +1317,7 @@ class BuildInfoHelper(object): log_information['message'] = event.msg log_information['pathname'] = event.pathname log_information['lineno'] = event.lineno - logger.info("Logging error 2: %s" % log_information) + logger.info("Logging error 2: %s", log_information) self.orm_wrapper.create_logmessage(log_information) def close(self, errorcode):
@@ -1320,7 +1332,7 @@ class BuildInfoHelper(object): else: # we have no build, and we still have events; something amazingly wrong happend for event in self.internal_state['backlog']: - logger.error("UNSAVED log: %s", event.msg) + logger.error("UNSAVED log: %s", event.msg) if not connection.features.autocommits_when_autocommit_is_off: transaction.set_autocommit(True)
lib/bb/ui/toasterui.py +27 -30
156 lines not on this page · raw message
Signature · 1 line
2.1.4
PATCH 13/14 toastergui: do not show in-progress builds in the table
In reply to Michael WoodFrom: Alexandru DAMIAN <[email protected]>
This patch fixes the all-builds table as not to show in-progress builds.
--- lib/toaster/toastergui/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
lib/toaster/toastergui/views.py +1 -1
@@ -1892,7 +1892,7 @@ if True: # be able to display something. 'count' and 'page' are mandatory for all views # that use paginators. - queryset = Build.objects.filter(outcome__lte = Build.IN_PROGRESS) + queryset = Build.objects.exclude(outcome = Build.IN_PROGRESS) context, pagesize, orderby = _build_list_helper(request, queryset)
Signature · 1 line
2.1.4
PATCH 14/14 toastergui: fix projectbuilds page
In reply to Michael WoodFrom: Alexandru DAMIAN <[email protected]>
This patch fixes the redirection projectbuilds page and the template layout in the projectbuilds page.
* The _build_list_helper now returns an empty RedirectException that is properly customized by the caller and re-raised to achieve redirection to the original page (poor man's overloading)
* The template for ProjectBuilds is updated as to properly display Build objects instead of BuildRequest objects.
[YOCTO #7995]
--- .../toastergui/templates/projectbuilds.html | 42 +++------------------- lib/toaster/toastergui/views.py | 24 +++++++++---- 2 files changed, 21 insertions(+), 45 deletions(-)
lib/toaster/toastergui/templates/projectbuilds.html +4 -38
@@ -16,8 +16,8 @@ <script> // initialize the date range controls $(document).ready(function () { - date_init('created','{{last_date_from}}','{{last_date_to}}','{{dateMin_created}}','{{dateMax_created}}','{{daterange_selected}}'); - date_init('updated','{{last_date_from}}','{{last_date_to}}','{{dateMin_updated}}','{{dateMax_updated}}','{{daterange_selected}}'); + date_init('created','{{last_date_from}}','{{last_date_to}}','{{dateMin_started_on}}','{{dateMax_started_on}}','{{daterange_selected}}'); + date_init('updated','{{last_date_from}}','{{last_date_to}}','{{dateMin_completed_on}}','{{dateMax_completed_on}}','{{daterange_selected}}'); }); </script>
@@ -28,7 +28,7 @@ {%elif request.GET.filter and objects.paginator.count == 0 or request.GET.search and objects.paginator.count == 0 %} No builds found {%else%} - All builds + Project builds {%endif%} <i class="icon-question-sign get-help heading-help" title="This page lists all the builds for the current project"></i> </h1>
@@ -58,7 +58,7 @@ {% include "basetable_top.html" %} <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work --> - {% for br in objects %}{% if br.build %} {% with build=br.build %} {# if we have a build, just display it #} + {% for build in objects %} {# if we have a build, just display it #} <tr class="data"> <td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a> {% if build.project %}
@@ -96,40 +96,6 @@ {% endif %} </td> </tr> - - {%endwith%} - {% else %} {# we don't have a build for this build request, mask the data with build request data #} - - - - <tr class="data"> - <td class="outcome">{% if br.state == br.REQ_FAILED %}<i class="icon-minus-sign error"></i>{%else%}FIXME_build_request_state{%endif%}</td> - <td class="target"> - <a href="{% url "builddashboard" br.id %}"><span data-toggle="tooltip" {%if br.brtarget_set.all.count > 1%}title="Targets: {%for target in br.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{br.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} </span></a> - </td> - <td class="machine"> - <a href="{% url "builddashboard" br.id %}">{{br.machine}}</a> - </td> - <td class="started_on"> - <a href="{% url "builddashboard" br.id %}">{{br.created|date:"d/m/y H:i"}}</a> - </td> - <td class="completed_on"> - <a href="{% url "builddashboard" br.id %}">{{br.updated|date:"d/m/y H:i"}}</a> - </td> - <td class="failed_tasks error"> - </td> - <td class="errors.count"> - <a class="errors.count error" href="{% url "builddashboard" br.id %}#errors">{{br.brerror_set.all.count}} error{{br.brerror_set.all.count|pluralize}}</a> - </td> - <td class="warnings.count"> - </td> - <td class="time"> - {{br.timespent.total_seconds|sectohms}} - </td> - <td class="output"> {# we have no output here #} - </td> - </tr> - {%endif%} {% endfor %}
lib/toaster/toastergui/views.py +17 -7
@@ -33,7 +33,7 @@ from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact from bldcontrol import bbcontroller from django.views.decorators.cache import cache_control -from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse, resolve from django.core.exceptions import MultipleObjectsReturned from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.http import HttpResponseBadRequest, HttpResponseNotFound
@@ -372,7 +372,6 @@ def _get_queryset(model, queryset, filter_string, search_term, ordering_string, # if the value is given explicitly as a GET parameter it will be the first selected, # otherwise the cookie value will be used. def _get_parameters_values(request, default_count, default_order): - from django.core.urlresolvers import resolve current_url = resolve(request.path_info).url_name pagesize = request.GET.get('count', request.session.get('%s_count' % current_url, default_count)) orderby = request.GET.get('orderby', request.session.get('%s_orderby' % current_url, default_order))
@@ -1894,7 +1893,14 @@ if True: queryset = Build.objects.exclude(outcome = Build.IN_PROGRESS) - context, pagesize, orderby = _build_list_helper(request, queryset) + try: + context, pagesize, orderby = _build_list_helper(request, queryset) + # all builds page as a Project column + context['tablecols'].append({'name': 'Project', 'clcalss': 'project_column', }) + except RedirectException as re: + # rewrite the RedirectException + re.view = resolve(request.path_info).url_name + raise re _set_parameters_values(pagesize, orderby, request) return context
@@ -1908,7 +1914,7 @@ if True: mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } retval = _verify_parameters( request.GET, mandatory_parameters ) if retval: - raise RedirectException( 'all-builds', request.GET, mandatory_parameters) + raise RedirectException( None, request.GET, mandatory_parameters) # boilerplate code that takes a request for an object type and returns a queryset # for that object type. copypasta for all needed table searches
@@ -2083,8 +2089,6 @@ if True: {'name': 'Image files', 'clclass': 'output', 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory", # TODO: compute image fstypes from Target_Image_File - }, - {'name': 'Project', 'clcalss': 'project_column', } ] }
@@ -2655,7 +2659,13 @@ if True: queryset = Build.objects.filter(outcome__lte = Build.IN_PROGRESS) - context, pagesize, orderby = _build_list_helper(request, queryset) + try: + context, pagesize, orderby = _build_list_helper(request, queryset) + except RedirectException as re: + # rewrite the RedirectException with our current url information + re.view = resolve(request.path_info).url_name + re.okwargs = {"pid" : pid} + raise re context['project'] = prj _set_parameters_values(pagesize, orderby, request)
Signature · 1 line
2.1.4
Re: [PATCH 12/14] bitbake: toaster logger: fix pylint issues
In reply to Michael WoodExcerpts from Michael Wood's message of 2015-08-18 18:28:58 +0200:
Quoted 5 lines
From: Alexandru DAMIAN <[email protected]>
This patch fixes pylint issues in the toaster build data logger, toasterui. The following types of warnings are touched here:
Quoted 8 lines
* fixing imports
* unused variables are set to _
* logger calls now use lazy evaluation instead of formatting the string
* correct whitespace identation
* removes unneeded "pass" statements, and extra parantheses
* disable specific pylint warnings when decideing to override them
This change causes the following RuntimeError exception, when running bitbake (any bitbake command):
$ python --version
Python 2.7.9
$ bitbake
Traceback (most recent call last):
File ".../poky/bitbake/bin/bitbake", line 44, in <module>
sys.exit(bitbake_main(BitBakeConfigParameters(sys.argv),
File ".../poky/bitbake/lib/bb/cookerdata.py", line 37, in __init__
self.options, targets = self.parseCommandLine(argv)
File ".../poky/bitbake/lib/bb/main.py", line 178, in parseCommandLine
valid_uis = list_extension_modules(bb.ui, 'main')
File ".../poky/bitbake/lib/bb/main.py", line 62, in list_extension_modules
module = __import__(pkg.__name__, fromlist=[modulename])
File ".../poky/bitbake/lib/bb/ui/buildinfohelper.py", line 44, in <module>
from bldcontrol.models import BuildEnvironment, BuildRequest
File ".../poky/bitbake/lib/toaster/bldcontrol/models.py", line 3, in <module>
from orm.models import Project, ProjectLayer, ProjectVariable, ProjectTarget, Build
File ".../poky/bitbake/lib/toaster/orm/models.py", line 56, in <module>
class ToasterSetting(models.Model):
File "/usr/lib/python2.7/dist-packages/django/db/models/base.py", line 283, in __new__
new_class._meta.apps.register_model(new_class._meta.app_label, new_class)
File "/usr/lib/python2.7/dist-packages/django/apps/registry.py", line 221, in register_model
(model_name, app_label, app_models[model_name], model))
RuntimeError: Conflicting 'toastersetting' models in application 'orm': <class 'toaster.orm.models.ToasterSetting'> and <class 'orm.models.ToasterSetting'>.Even though .../poky/bitbake/lib is already in the sys.path, .../poky/bitbake/lib/toaster is also added, which seem to be the cause of this issue.
I'm a bit surprised that this kind of change causes issues for use of bitbake not related to toaster.
Signature · 1 line
olof johansson
Re: [PATCH 12/14] bitbake: toaster logger: fix pylint issues
Quoted 42 lines · Olof Johansson
Excerpts from Michael Wood's message of 2015-08-18 18:28:58 +0200:
Quoted 5 lines
From: Alexandru DAMIAN <[email protected]>
This patch fixes pylint issues in the toaster build data logger, toasterui. The following types of warnings are touched here:
Quoted 8 lines
* fixing imports
* unused variables are set to _
* logger calls now use lazy evaluation instead of formatting the string
* correct whitespace identation
* removes unneeded "pass" statements, and extra parantheses
* disable specific pylint warnings when decideing to override themThis change causes the following RuntimeError exception, when running bitbake (any bitbake command):
$ python --version Python 2.7.9 $ bitbake Traceback (most recent call last): File ".../poky/bitbake/bin/bitbake", line 44, in <module> sys.exit(bitbake_main(BitBakeConfigParameters(sys.argv), File ".../poky/bitbake/lib/bb/cookerdata.py", line 37, in __init__ self.options, targets = self.parseCommandLine(argv) File ".../poky/bitbake/lib/bb/main.py", line 178, in parseCommandLine valid_uis = list_extension_modules(bb.ui, 'main') File ".../poky/bitbake/lib/bb/main.py", line 62, in list_extension_modules module = __import__(pkg.__name__, fromlist=[modulename]) File ".../poky/bitbake/lib/bb/ui/buildinfohelper.py", line 44, in <module> from bldcontrol.models import BuildEnvironment, BuildRequest File ".../poky/bitbake/lib/toaster/bldcontrol/models.py", line 3, in <module> from orm.models import Project, ProjectLayer, ProjectVariable, ProjectTarget, Build File ".../poky/bitbake/lib/toaster/orm/models.py", line 56, in <module> class ToasterSetting(models.Model): File "/usr/lib/python2.7/dist-packages/django/db/models/base.py", line 283, in __new__ new_class._meta.apps.register_model(new_class._meta.app_label, new_class) File "/usr/lib/python2.7/dist-packages/django/apps/registry.py", line 221, in register_model (model_name, app_label, app_models[model_name], model)) RuntimeError: Conflicting 'toastersetting' models in application 'orm': <class 'toaster.orm.models.ToasterSetting'> and <class 'orm.models.ToasterSetting'>.
Quoted 6 lines
Even though .../poky/bitbake/lib is already in the sys.path, .../poky/bitbake/lib/toaster is also added, which seem to be the cause of this issue.
I'm a bit surprised that this kind of change causes issues for use of bitbake not related to toaster.
We're all a bit surprised. Its a nasty effect of Pauls recent "list all the modules in bitbake --help" change :(.
Randy's sent I patch I've merged which at least bandaids this issue...
Cheers,
Richard