1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# BitBake ToasterUI Implementation 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# Copyright (C) 2013 Intel Corporation 5*4882a593Smuzhiyun# 6*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 7*4882a593Smuzhiyun# 8*4882a593Smuzhiyun 9*4882a593Smuzhiyunimport sys 10*4882a593Smuzhiyunimport bb 11*4882a593Smuzhiyunimport re 12*4882a593Smuzhiyunimport os 13*4882a593Smuzhiyun 14*4882a593Smuzhiyunimport django 15*4882a593Smuzhiyunfrom django.utils import timezone 16*4882a593Smuzhiyun 17*4882a593Smuzhiyunimport toaster 18*4882a593Smuzhiyun# Add toaster module to the search path to help django.setup() find the right 19*4882a593Smuzhiyun# modules 20*4882a593Smuzhiyunsys.path.insert(0, os.path.dirname(toaster.__file__)) 21*4882a593Smuzhiyun 22*4882a593Smuzhiyun#Set the DJANGO_SETTINGS_MODULE if it's not already set 23*4882a593Smuzhiyunos.environ["DJANGO_SETTINGS_MODULE"] =\ 24*4882a593Smuzhiyun os.environ.get("DJANGO_SETTINGS_MODULE", 25*4882a593Smuzhiyun "toaster.toastermain.settings") 26*4882a593Smuzhiyun# Setup django framework (needs to be done before importing modules) 27*4882a593Smuzhiyundjango.setup() 28*4882a593Smuzhiyun 29*4882a593Smuzhiyunfrom orm.models import Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage, HelpText 30*4882a593Smuzhiyunfrom orm.models import Target_Image_File, TargetKernelFile, TargetSDKFile 31*4882a593Smuzhiyunfrom orm.models import Variable, VariableHistory 32*4882a593Smuzhiyunfrom orm.models import Package, Package_File, Target_Installed_Package, Target_File 33*4882a593Smuzhiyunfrom orm.models import Task_Dependency, Package_Dependency 34*4882a593Smuzhiyunfrom orm.models import Recipe_Dependency, Provides 35*4882a593Smuzhiyunfrom orm.models import Project, CustomImagePackage 36*4882a593Smuzhiyunfrom orm.models import signal_runbuilds 37*4882a593Smuzhiyun 38*4882a593Smuzhiyunfrom bldcontrol.models import BuildEnvironment, BuildRequest 39*4882a593Smuzhiyunfrom bldcontrol.models import BRLayer 40*4882a593Smuzhiyunfrom bldcontrol import bbcontroller 41*4882a593Smuzhiyun 42*4882a593Smuzhiyunfrom bb.msg import BBLogFormatter as formatter 43*4882a593Smuzhiyunfrom django.db import models 44*4882a593Smuzhiyunfrom pprint import pformat 45*4882a593Smuzhiyunimport logging 46*4882a593Smuzhiyunfrom datetime import datetime, timedelta 47*4882a593Smuzhiyun 48*4882a593Smuzhiyunfrom django.db import transaction 49*4882a593Smuzhiyun 50*4882a593Smuzhiyun 51*4882a593Smuzhiyun# pylint: disable=invalid-name 52*4882a593Smuzhiyun# the logger name is standard throughout BitBake 53*4882a593Smuzhiyunlogger = logging.getLogger("ToasterLogger") 54*4882a593Smuzhiyun 55*4882a593Smuzhiyunclass NotExisting(Exception): 56*4882a593Smuzhiyun pass 57*4882a593Smuzhiyun 58*4882a593Smuzhiyunclass ORMWrapper(object): 59*4882a593Smuzhiyun """ This class creates the dictionaries needed to store information in the database 60*4882a593Smuzhiyun following the format defined by the Django models. It is also used to save this 61*4882a593Smuzhiyun information in the database. 62*4882a593Smuzhiyun """ 63*4882a593Smuzhiyun 64*4882a593Smuzhiyun def __init__(self): 65*4882a593Smuzhiyun self.layer_version_objects = [] 66*4882a593Smuzhiyun self.layer_version_built = [] 67*4882a593Smuzhiyun self.task_objects = {} 68*4882a593Smuzhiyun self.recipe_objects = {} 69*4882a593Smuzhiyun 70*4882a593Smuzhiyun @staticmethod 71*4882a593Smuzhiyun def _build_key(**kwargs): 72*4882a593Smuzhiyun key = "0" 73*4882a593Smuzhiyun for k in sorted(kwargs.keys()): 74*4882a593Smuzhiyun if isinstance(kwargs[k], models.Model): 75*4882a593Smuzhiyun key += "-%d" % kwargs[k].id 76*4882a593Smuzhiyun else: 77*4882a593Smuzhiyun key += "-%s" % str(kwargs[k]) 78*4882a593Smuzhiyun return key 79*4882a593Smuzhiyun 80*4882a593Smuzhiyun 81*4882a593Smuzhiyun def _cached_get_or_create(self, clazz, **kwargs): 82*4882a593Smuzhiyun """ This is a memory-cached get_or_create. We assume that the objects will not be created in the 83*4882a593Smuzhiyun database through any other means. 84*4882a593Smuzhiyun """ 85*4882a593Smuzhiyun 86*4882a593Smuzhiyun assert issubclass(clazz, models.Model), "_cached_get_or_create needs to get the class as first argument" 87*4882a593Smuzhiyun 88*4882a593Smuzhiyun key = ORMWrapper._build_key(**kwargs) 89*4882a593Smuzhiyun dictname = "objects_%s" % clazz.__name__ 90*4882a593Smuzhiyun if not dictname in vars(self).keys(): 91*4882a593Smuzhiyun vars(self)[dictname] = {} 92*4882a593Smuzhiyun 93*4882a593Smuzhiyun created = False 94*4882a593Smuzhiyun if not key in vars(self)[dictname].keys(): 95*4882a593Smuzhiyun vars(self)[dictname][key], created = \ 96*4882a593Smuzhiyun clazz.objects.get_or_create(**kwargs) 97*4882a593Smuzhiyun 98*4882a593Smuzhiyun return (vars(self)[dictname][key], created) 99*4882a593Smuzhiyun 100*4882a593Smuzhiyun 101*4882a593Smuzhiyun def _cached_get(self, clazz, **kwargs): 102*4882a593Smuzhiyun """ This is a memory-cached get. We assume that the objects will not change in the database between gets. 103*4882a593Smuzhiyun """ 104*4882a593Smuzhiyun assert issubclass(clazz, models.Model), "_cached_get needs to get the class as first argument" 105*4882a593Smuzhiyun 106*4882a593Smuzhiyun key = ORMWrapper._build_key(**kwargs) 107*4882a593Smuzhiyun dictname = "objects_%s" % clazz.__name__ 108*4882a593Smuzhiyun 109*4882a593Smuzhiyun if not dictname in vars(self).keys(): 110*4882a593Smuzhiyun vars(self)[dictname] = {} 111*4882a593Smuzhiyun 112*4882a593Smuzhiyun if not key in vars(self)[dictname].keys(): 113*4882a593Smuzhiyun vars(self)[dictname][key] = clazz.objects.get(**kwargs) 114*4882a593Smuzhiyun 115*4882a593Smuzhiyun return vars(self)[dictname][key] 116*4882a593Smuzhiyun 117*4882a593Smuzhiyun def get_similar_target_with_image_files(self, target): 118*4882a593Smuzhiyun """ 119*4882a593Smuzhiyun Get a Target object "similar" to target; i.e. with the same target 120*4882a593Smuzhiyun name ('core-image-minimal' etc.) and machine. 121*4882a593Smuzhiyun """ 122*4882a593Smuzhiyun return target.get_similar_target_with_image_files() 123*4882a593Smuzhiyun 124*4882a593Smuzhiyun def get_similar_target_with_sdk_files(self, target): 125*4882a593Smuzhiyun return target.get_similar_target_with_sdk_files() 126*4882a593Smuzhiyun 127*4882a593Smuzhiyun def clone_image_artifacts(self, target_from, target_to): 128*4882a593Smuzhiyun target_to.clone_image_artifacts_from(target_from) 129*4882a593Smuzhiyun 130*4882a593Smuzhiyun def clone_sdk_artifacts(self, target_from, target_to): 131*4882a593Smuzhiyun target_to.clone_sdk_artifacts_from(target_from) 132*4882a593Smuzhiyun 133*4882a593Smuzhiyun def _timestamp_to_datetime(self, secs): 134*4882a593Smuzhiyun """ 135*4882a593Smuzhiyun Convert timestamp in seconds to Python datetime 136*4882a593Smuzhiyun """ 137*4882a593Smuzhiyun return timezone.make_aware(datetime(1970, 1, 1) + timedelta(seconds=secs)) 138*4882a593Smuzhiyun 139*4882a593Smuzhiyun # pylint: disable=no-self-use 140*4882a593Smuzhiyun # we disable detection of no self use in functions because the methods actually work on the object 141*4882a593Smuzhiyun # even if they don't touch self anywhere 142*4882a593Smuzhiyun 143*4882a593Smuzhiyun # pylint: disable=bad-continuation 144*4882a593Smuzhiyun # we do not follow the python conventions for continuation indentation due to long lines here 145*4882a593Smuzhiyun 146*4882a593Smuzhiyun def get_or_create_build_object(self, brbe): 147*4882a593Smuzhiyun prj = None 148*4882a593Smuzhiyun buildrequest = None 149*4882a593Smuzhiyun if brbe is not None: 150*4882a593Smuzhiyun # Toaster-triggered build 151*4882a593Smuzhiyun logger.debug("buildinfohelper: brbe is %s" % brbe) 152*4882a593Smuzhiyun br, _ = brbe.split(":") 153*4882a593Smuzhiyun buildrequest = BuildRequest.objects.get(pk=br) 154*4882a593Smuzhiyun prj = buildrequest.project 155*4882a593Smuzhiyun else: 156*4882a593Smuzhiyun # CLI build 157*4882a593Smuzhiyun prj = Project.objects.get_or_create_default_project() 158*4882a593Smuzhiyun logger.debug("buildinfohelper: project is not specified, defaulting to %s" % prj) 159*4882a593Smuzhiyun 160*4882a593Smuzhiyun if buildrequest is not None: 161*4882a593Smuzhiyun # reuse existing Build object 162*4882a593Smuzhiyun build = buildrequest.build 163*4882a593Smuzhiyun build.project = prj 164*4882a593Smuzhiyun build.save() 165*4882a593Smuzhiyun else: 166*4882a593Smuzhiyun # create new Build object 167*4882a593Smuzhiyun now = timezone.now() 168*4882a593Smuzhiyun build = Build.objects.create( 169*4882a593Smuzhiyun project=prj, 170*4882a593Smuzhiyun started_on=now, 171*4882a593Smuzhiyun completed_on=now, 172*4882a593Smuzhiyun build_name='') 173*4882a593Smuzhiyun 174*4882a593Smuzhiyun logger.debug("buildinfohelper: build is created %s" % build) 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun if buildrequest is not None: 177*4882a593Smuzhiyun buildrequest.build = build 178*4882a593Smuzhiyun buildrequest.save() 179*4882a593Smuzhiyun 180*4882a593Smuzhiyun return build 181*4882a593Smuzhiyun 182*4882a593Smuzhiyun def update_build(self, build, data_dict): 183*4882a593Smuzhiyun for key in data_dict: 184*4882a593Smuzhiyun setattr(build, key, data_dict[key]) 185*4882a593Smuzhiyun build.save() 186*4882a593Smuzhiyun 187*4882a593Smuzhiyun @staticmethod 188*4882a593Smuzhiyun def get_or_create_targets(target_info): 189*4882a593Smuzhiyun """ 190*4882a593Smuzhiyun NB get_or_create() is used here because for Toaster-triggered builds, 191*4882a593Smuzhiyun we already created the targets when the build was triggered. 192*4882a593Smuzhiyun """ 193*4882a593Smuzhiyun result = [] 194*4882a593Smuzhiyun for target in target_info['targets']: 195*4882a593Smuzhiyun task = '' 196*4882a593Smuzhiyun if ':' in target: 197*4882a593Smuzhiyun target, task = target.split(':', 1) 198*4882a593Smuzhiyun if task.startswith('do_'): 199*4882a593Smuzhiyun task = task[3:] 200*4882a593Smuzhiyun if task == 'build': 201*4882a593Smuzhiyun task = '' 202*4882a593Smuzhiyun 203*4882a593Smuzhiyun obj, _ = Target.objects.get_or_create(build=target_info['build'], 204*4882a593Smuzhiyun target=target, 205*4882a593Smuzhiyun task=task) 206*4882a593Smuzhiyun result.append(obj) 207*4882a593Smuzhiyun return result 208*4882a593Smuzhiyun 209*4882a593Smuzhiyun def update_build_stats_and_outcome(self, build, errors, warnings, taskfailures): 210*4882a593Smuzhiyun assert isinstance(build,Build) 211*4882a593Smuzhiyun assert isinstance(errors, int) 212*4882a593Smuzhiyun assert isinstance(warnings, int) 213*4882a593Smuzhiyun 214*4882a593Smuzhiyun if build.outcome == Build.CANCELLED: 215*4882a593Smuzhiyun return 216*4882a593Smuzhiyun try: 217*4882a593Smuzhiyun if build.buildrequest.state == BuildRequest.REQ_CANCELLING: 218*4882a593Smuzhiyun return 219*4882a593Smuzhiyun except AttributeError: 220*4882a593Smuzhiyun # We may not have a buildrequest if this is a command line build 221*4882a593Smuzhiyun pass 222*4882a593Smuzhiyun 223*4882a593Smuzhiyun outcome = Build.SUCCEEDED 224*4882a593Smuzhiyun if errors or taskfailures: 225*4882a593Smuzhiyun outcome = Build.FAILED 226*4882a593Smuzhiyun 227*4882a593Smuzhiyun build.completed_on = timezone.now() 228*4882a593Smuzhiyun build.outcome = outcome 229*4882a593Smuzhiyun build.save() 230*4882a593Smuzhiyun 231*4882a593Smuzhiyun # We force a sync point here to force the outcome status commit, 232*4882a593Smuzhiyun # which resolves a race condition with the build completion takedown 233*4882a593Smuzhiyun transaction.set_autocommit(True) 234*4882a593Smuzhiyun transaction.set_autocommit(False) 235*4882a593Smuzhiyun 236*4882a593Smuzhiyun signal_runbuilds() 237*4882a593Smuzhiyun 238*4882a593Smuzhiyun def update_target_set_license_manifest(self, target, license_manifest_path): 239*4882a593Smuzhiyun target.license_manifest_path = license_manifest_path 240*4882a593Smuzhiyun target.save() 241*4882a593Smuzhiyun 242*4882a593Smuzhiyun def update_target_set_package_manifest(self, target, package_manifest_path): 243*4882a593Smuzhiyun target.package_manifest_path = package_manifest_path 244*4882a593Smuzhiyun target.save() 245*4882a593Smuzhiyun 246*4882a593Smuzhiyun def update_task_object(self, build, task_name, recipe_name, task_stats): 247*4882a593Smuzhiyun """ 248*4882a593Smuzhiyun Find the task for build which matches the recipe and task name 249*4882a593Smuzhiyun to be stored 250*4882a593Smuzhiyun """ 251*4882a593Smuzhiyun task_to_update = Task.objects.get( 252*4882a593Smuzhiyun build = build, 253*4882a593Smuzhiyun task_name = task_name, 254*4882a593Smuzhiyun recipe__name = recipe_name 255*4882a593Smuzhiyun ) 256*4882a593Smuzhiyun 257*4882a593Smuzhiyun if 'started' in task_stats and 'ended' in task_stats: 258*4882a593Smuzhiyun task_to_update.started = self._timestamp_to_datetime(task_stats['started']) 259*4882a593Smuzhiyun task_to_update.ended = self._timestamp_to_datetime(task_stats['ended']) 260*4882a593Smuzhiyun task_to_update.elapsed_time = (task_stats['ended'] - task_stats['started']) 261*4882a593Smuzhiyun task_to_update.cpu_time_user = task_stats.get('cpu_time_user') 262*4882a593Smuzhiyun task_to_update.cpu_time_system = task_stats.get('cpu_time_system') 263*4882a593Smuzhiyun if 'disk_io_read' in task_stats and 'disk_io_write' in task_stats: 264*4882a593Smuzhiyun task_to_update.disk_io_read = task_stats['disk_io_read'] 265*4882a593Smuzhiyun task_to_update.disk_io_write = task_stats['disk_io_write'] 266*4882a593Smuzhiyun task_to_update.disk_io = task_stats['disk_io_read'] + task_stats['disk_io_write'] 267*4882a593Smuzhiyun 268*4882a593Smuzhiyun task_to_update.save() 269*4882a593Smuzhiyun 270*4882a593Smuzhiyun def get_update_task_object(self, task_information, must_exist = False): 271*4882a593Smuzhiyun assert 'build' in task_information 272*4882a593Smuzhiyun assert 'recipe' in task_information 273*4882a593Smuzhiyun assert 'task_name' in task_information 274*4882a593Smuzhiyun 275*4882a593Smuzhiyun # we use must_exist info for database look-up optimization 276*4882a593Smuzhiyun task_object, created = self._cached_get_or_create(Task, 277*4882a593Smuzhiyun build=task_information['build'], 278*4882a593Smuzhiyun recipe=task_information['recipe'], 279*4882a593Smuzhiyun task_name=task_information['task_name'] 280*4882a593Smuzhiyun ) 281*4882a593Smuzhiyun if created and must_exist: 282*4882a593Smuzhiyun task_information['debug'] = "build id %d, recipe id %d" % (task_information['build'].pk, task_information['recipe'].pk) 283*4882a593Smuzhiyun raise NotExisting("Task object created when expected to exist", task_information) 284*4882a593Smuzhiyun 285*4882a593Smuzhiyun object_changed = False 286*4882a593Smuzhiyun for v in vars(task_object): 287*4882a593Smuzhiyun if v in task_information.keys(): 288*4882a593Smuzhiyun if vars(task_object)[v] != task_information[v]: 289*4882a593Smuzhiyun vars(task_object)[v] = task_information[v] 290*4882a593Smuzhiyun object_changed = True 291*4882a593Smuzhiyun 292*4882a593Smuzhiyun # update setscene-related information if the task has a setscene 293*4882a593Smuzhiyun if task_object.outcome == Task.OUTCOME_COVERED and 1 == task_object.get_related_setscene().count(): 294*4882a593Smuzhiyun task_object.outcome = Task.OUTCOME_CACHED 295*4882a593Smuzhiyun object_changed = True 296*4882a593Smuzhiyun 297*4882a593Smuzhiyun outcome_task_setscene = Task.objects.get(task_executed=True, build = task_object.build, 298*4882a593Smuzhiyun recipe = task_object.recipe, task_name=task_object.task_name+"_setscene").outcome 299*4882a593Smuzhiyun if outcome_task_setscene == Task.OUTCOME_SUCCESS: 300*4882a593Smuzhiyun task_object.sstate_result = Task.SSTATE_RESTORED 301*4882a593Smuzhiyun object_changed = True 302*4882a593Smuzhiyun elif outcome_task_setscene == Task.OUTCOME_FAILED: 303*4882a593Smuzhiyun task_object.sstate_result = Task.SSTATE_FAILED 304*4882a593Smuzhiyun object_changed = True 305*4882a593Smuzhiyun 306*4882a593Smuzhiyun if object_changed: 307*4882a593Smuzhiyun task_object.save() 308*4882a593Smuzhiyun return task_object 309*4882a593Smuzhiyun 310*4882a593Smuzhiyun 311*4882a593Smuzhiyun def get_update_recipe_object(self, recipe_information, must_exist = False): 312*4882a593Smuzhiyun assert 'layer_version' in recipe_information 313*4882a593Smuzhiyun assert 'file_path' in recipe_information 314*4882a593Smuzhiyun assert 'pathflags' in recipe_information 315*4882a593Smuzhiyun 316*4882a593Smuzhiyun assert not recipe_information['file_path'].startswith("/") # we should have layer-relative paths at all times 317*4882a593Smuzhiyun 318*4882a593Smuzhiyun 319*4882a593Smuzhiyun def update_recipe_obj(recipe_object): 320*4882a593Smuzhiyun object_changed = False 321*4882a593Smuzhiyun for v in vars(recipe_object): 322*4882a593Smuzhiyun if v in recipe_information.keys(): 323*4882a593Smuzhiyun object_changed = True 324*4882a593Smuzhiyun vars(recipe_object)[v] = recipe_information[v] 325*4882a593Smuzhiyun 326*4882a593Smuzhiyun if object_changed: 327*4882a593Smuzhiyun recipe_object.save() 328*4882a593Smuzhiyun 329*4882a593Smuzhiyun recipe, created = self._cached_get_or_create(Recipe, layer_version=recipe_information['layer_version'], 330*4882a593Smuzhiyun file_path=recipe_information['file_path'], pathflags = recipe_information['pathflags']) 331*4882a593Smuzhiyun 332*4882a593Smuzhiyun update_recipe_obj(recipe) 333*4882a593Smuzhiyun 334*4882a593Smuzhiyun built_recipe = None 335*4882a593Smuzhiyun # Create a copy of the recipe for historical puposes and update it 336*4882a593Smuzhiyun for built_layer in self.layer_version_built: 337*4882a593Smuzhiyun if built_layer.layer == recipe_information['layer_version'].layer: 338*4882a593Smuzhiyun built_recipe, c = self._cached_get_or_create(Recipe, 339*4882a593Smuzhiyun layer_version=built_layer, 340*4882a593Smuzhiyun file_path=recipe_information['file_path'], 341*4882a593Smuzhiyun pathflags = recipe_information['pathflags']) 342*4882a593Smuzhiyun update_recipe_obj(built_recipe) 343*4882a593Smuzhiyun break 344*4882a593Smuzhiyun 345*4882a593Smuzhiyun 346*4882a593Smuzhiyun # If we're in analysis mode or if this is a custom recipe 347*4882a593Smuzhiyun # then we are wholly responsible for the data 348*4882a593Smuzhiyun # and therefore we return the 'real' recipe rather than the build 349*4882a593Smuzhiyun # history copy of the recipe. 350*4882a593Smuzhiyun if recipe_information['layer_version'].build is not None and \ 351*4882a593Smuzhiyun recipe_information['layer_version'].build.project == \ 352*4882a593Smuzhiyun Project.objects.get_or_create_default_project(): 353*4882a593Smuzhiyun return recipe 354*4882a593Smuzhiyun 355*4882a593Smuzhiyun if built_recipe is None: 356*4882a593Smuzhiyun return recipe 357*4882a593Smuzhiyun 358*4882a593Smuzhiyun return built_recipe 359*4882a593Smuzhiyun 360*4882a593Smuzhiyun def get_update_layer_version_object(self, build_obj, layer_obj, layer_version_information): 361*4882a593Smuzhiyun if isinstance(layer_obj, Layer_Version): 362*4882a593Smuzhiyun # We already found our layer version for this build so just 363*4882a593Smuzhiyun # update it with the new build information 364*4882a593Smuzhiyun logger.debug("We found our layer from toaster") 365*4882a593Smuzhiyun layer_obj.local_path = layer_version_information['local_path'] 366*4882a593Smuzhiyun layer_obj.save() 367*4882a593Smuzhiyun self.layer_version_objects.append(layer_obj) 368*4882a593Smuzhiyun 369*4882a593Smuzhiyun # create a new copy of this layer version as a snapshot for 370*4882a593Smuzhiyun # historical purposes 371*4882a593Smuzhiyun layer_copy, c = Layer_Version.objects.get_or_create( 372*4882a593Smuzhiyun build=build_obj, 373*4882a593Smuzhiyun layer=layer_obj.layer, 374*4882a593Smuzhiyun release=layer_obj.release, 375*4882a593Smuzhiyun branch=layer_version_information['branch'], 376*4882a593Smuzhiyun commit=layer_version_information['commit'], 377*4882a593Smuzhiyun local_path=layer_version_information['local_path'], 378*4882a593Smuzhiyun ) 379*4882a593Smuzhiyun 380*4882a593Smuzhiyun logger.debug("Created new layer version %s for build history", 381*4882a593Smuzhiyun layer_copy.layer.name) 382*4882a593Smuzhiyun 383*4882a593Smuzhiyun self.layer_version_built.append(layer_copy) 384*4882a593Smuzhiyun 385*4882a593Smuzhiyun return layer_obj 386*4882a593Smuzhiyun 387*4882a593Smuzhiyun assert isinstance(build_obj, Build) 388*4882a593Smuzhiyun assert isinstance(layer_obj, Layer) 389*4882a593Smuzhiyun assert 'branch' in layer_version_information 390*4882a593Smuzhiyun assert 'commit' in layer_version_information 391*4882a593Smuzhiyun assert 'priority' in layer_version_information 392*4882a593Smuzhiyun assert 'local_path' in layer_version_information 393*4882a593Smuzhiyun 394*4882a593Smuzhiyun # If we're doing a command line build then associate this new layer with the 395*4882a593Smuzhiyun # project to avoid it 'contaminating' toaster data 396*4882a593Smuzhiyun project = None 397*4882a593Smuzhiyun if build_obj.project == Project.objects.get_or_create_default_project(): 398*4882a593Smuzhiyun project = build_obj.project 399*4882a593Smuzhiyun 400*4882a593Smuzhiyun layer_version_object, _ = Layer_Version.objects.get_or_create( 401*4882a593Smuzhiyun build = build_obj, 402*4882a593Smuzhiyun layer = layer_obj, 403*4882a593Smuzhiyun branch = layer_version_information['branch'], 404*4882a593Smuzhiyun commit = layer_version_information['commit'], 405*4882a593Smuzhiyun priority = layer_version_information['priority'], 406*4882a593Smuzhiyun local_path = layer_version_information['local_path'], 407*4882a593Smuzhiyun project=project) 408*4882a593Smuzhiyun 409*4882a593Smuzhiyun self.layer_version_objects.append(layer_version_object) 410*4882a593Smuzhiyun 411*4882a593Smuzhiyun return layer_version_object 412*4882a593Smuzhiyun 413*4882a593Smuzhiyun def get_update_layer_object(self, layer_information, brbe): 414*4882a593Smuzhiyun assert 'name' in layer_information 415*4882a593Smuzhiyun assert 'layer_index_url' in layer_information 416*4882a593Smuzhiyun 417*4882a593Smuzhiyun # From command line builds we have no brbe as the request is directly 418*4882a593Smuzhiyun # from bitbake 419*4882a593Smuzhiyun if brbe is None: 420*4882a593Smuzhiyun # If we don't have git commit sha then we're using a non-git 421*4882a593Smuzhiyun # layer so set the layer_source_dir to identify it as such 422*4882a593Smuzhiyun if not layer_information['version']['commit']: 423*4882a593Smuzhiyun local_source_dir = layer_information["local_path"] 424*4882a593Smuzhiyun else: 425*4882a593Smuzhiyun local_source_dir = None 426*4882a593Smuzhiyun 427*4882a593Smuzhiyun layer_object, _ = \ 428*4882a593Smuzhiyun Layer.objects.get_or_create( 429*4882a593Smuzhiyun name=layer_information['name'], 430*4882a593Smuzhiyun local_source_dir=local_source_dir, 431*4882a593Smuzhiyun layer_index_url=layer_information['layer_index_url']) 432*4882a593Smuzhiyun 433*4882a593Smuzhiyun return layer_object 434*4882a593Smuzhiyun else: 435*4882a593Smuzhiyun br_id, be_id = brbe.split(":") 436*4882a593Smuzhiyun 437*4882a593Smuzhiyun # Find the layer version by matching the layer event information 438*4882a593Smuzhiyun # against the metadata we have in Toaster 439*4882a593Smuzhiyun 440*4882a593Smuzhiyun try: 441*4882a593Smuzhiyun br_layer = BRLayer.objects.get(req=br_id, 442*4882a593Smuzhiyun name=layer_information['name']) 443*4882a593Smuzhiyun return br_layer.layer_version 444*4882a593Smuzhiyun except (BRLayer.MultipleObjectsReturned, BRLayer.DoesNotExist): 445*4882a593Smuzhiyun # There are multiple of the same layer name or the name 446*4882a593Smuzhiyun # hasn't been determined by the toaster.bbclass layer 447*4882a593Smuzhiyun # so let's filter by the local_path 448*4882a593Smuzhiyun bc = bbcontroller.getBuildEnvironmentController(pk=be_id) 449*4882a593Smuzhiyun for br_layer in BRLayer.objects.filter(req=br_id): 450*4882a593Smuzhiyun if br_layer.giturl and \ 451*4882a593Smuzhiyun layer_information['local_path'].endswith( 452*4882a593Smuzhiyun bc.getGitCloneDirectory(br_layer.giturl, 453*4882a593Smuzhiyun br_layer.commit)): 454*4882a593Smuzhiyun return br_layer.layer_version 455*4882a593Smuzhiyun 456*4882a593Smuzhiyun if br_layer.local_source_dir == \ 457*4882a593Smuzhiyun layer_information['local_path']: 458*4882a593Smuzhiyun return br_layer.layer_version 459*4882a593Smuzhiyun 460*4882a593Smuzhiyun # We've reached the end of our search and couldn't find the layer 461*4882a593Smuzhiyun # we can continue but some data may be missing 462*4882a593Smuzhiyun raise NotExisting("Unidentified layer %s" % 463*4882a593Smuzhiyun pformat(layer_information)) 464*4882a593Smuzhiyun 465*4882a593Smuzhiyun def save_target_file_information(self, build_obj, target_obj, filedata): 466*4882a593Smuzhiyun assert isinstance(build_obj, Build) 467*4882a593Smuzhiyun assert isinstance(target_obj, Target) 468*4882a593Smuzhiyun dirs = filedata['dirs'] 469*4882a593Smuzhiyun files = filedata['files'] 470*4882a593Smuzhiyun syms = filedata['syms'] 471*4882a593Smuzhiyun 472*4882a593Smuzhiyun # always create the root directory as a special case; 473*4882a593Smuzhiyun # note that this is never displayed, so the owner, group, 474*4882a593Smuzhiyun # size, permission are irrelevant 475*4882a593Smuzhiyun tf_obj = Target_File.objects.create(target = target_obj, 476*4882a593Smuzhiyun path = '/', 477*4882a593Smuzhiyun size = 0, 478*4882a593Smuzhiyun owner = '', 479*4882a593Smuzhiyun group = '', 480*4882a593Smuzhiyun permission = '', 481*4882a593Smuzhiyun inodetype = Target_File.ITYPE_DIRECTORY) 482*4882a593Smuzhiyun tf_obj.save() 483*4882a593Smuzhiyun 484*4882a593Smuzhiyun # insert directories, ordered by name depth 485*4882a593Smuzhiyun for d in sorted(dirs, key=lambda x:len(x[-1].split("/"))): 486*4882a593Smuzhiyun (user, group, size) = d[1:4] 487*4882a593Smuzhiyun permission = d[0][1:] 488*4882a593Smuzhiyun path = d[4].lstrip(".") 489*4882a593Smuzhiyun 490*4882a593Smuzhiyun # we already created the root directory, so ignore any 491*4882a593Smuzhiyun # entry for it 492*4882a593Smuzhiyun if not path: 493*4882a593Smuzhiyun continue 494*4882a593Smuzhiyun 495*4882a593Smuzhiyun parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1]) 496*4882a593Smuzhiyun if not parent_path: 497*4882a593Smuzhiyun parent_path = "/" 498*4882a593Smuzhiyun parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY) 499*4882a593Smuzhiyun Target_File.objects.create( 500*4882a593Smuzhiyun target = target_obj, 501*4882a593Smuzhiyun path = path, 502*4882a593Smuzhiyun size = size, 503*4882a593Smuzhiyun inodetype = Target_File.ITYPE_DIRECTORY, 504*4882a593Smuzhiyun permission = permission, 505*4882a593Smuzhiyun owner = user, 506*4882a593Smuzhiyun group = group, 507*4882a593Smuzhiyun directory = parent_obj) 508*4882a593Smuzhiyun 509*4882a593Smuzhiyun 510*4882a593Smuzhiyun # we insert files 511*4882a593Smuzhiyun for d in files: 512*4882a593Smuzhiyun (user, group, size) = d[1:4] 513*4882a593Smuzhiyun permission = d[0][1:] 514*4882a593Smuzhiyun path = d[4].lstrip(".") 515*4882a593Smuzhiyun parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1]) 516*4882a593Smuzhiyun inodetype = Target_File.ITYPE_REGULAR 517*4882a593Smuzhiyun if d[0].startswith('b'): 518*4882a593Smuzhiyun inodetype = Target_File.ITYPE_BLOCK 519*4882a593Smuzhiyun if d[0].startswith('c'): 520*4882a593Smuzhiyun inodetype = Target_File.ITYPE_CHARACTER 521*4882a593Smuzhiyun if d[0].startswith('p'): 522*4882a593Smuzhiyun inodetype = Target_File.ITYPE_FIFO 523*4882a593Smuzhiyun 524*4882a593Smuzhiyun tf_obj = Target_File.objects.create( 525*4882a593Smuzhiyun target = target_obj, 526*4882a593Smuzhiyun path = path, 527*4882a593Smuzhiyun size = size, 528*4882a593Smuzhiyun inodetype = inodetype, 529*4882a593Smuzhiyun permission = permission, 530*4882a593Smuzhiyun owner = user, 531*4882a593Smuzhiyun group = group) 532*4882a593Smuzhiyun parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY) 533*4882a593Smuzhiyun tf_obj.directory = parent_obj 534*4882a593Smuzhiyun tf_obj.save() 535*4882a593Smuzhiyun 536*4882a593Smuzhiyun # we insert symlinks 537*4882a593Smuzhiyun for d in syms: 538*4882a593Smuzhiyun (user, group, size) = d[1:4] 539*4882a593Smuzhiyun permission = d[0][1:] 540*4882a593Smuzhiyun path = d[4].lstrip(".") 541*4882a593Smuzhiyun filetarget_path = d[6] 542*4882a593Smuzhiyun 543*4882a593Smuzhiyun parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1]) 544*4882a593Smuzhiyun if not filetarget_path.startswith("/"): 545*4882a593Smuzhiyun # we have a relative path, get a normalized absolute one 546*4882a593Smuzhiyun filetarget_path = parent_path + "/" + filetarget_path 547*4882a593Smuzhiyun fcp = filetarget_path.split("/") 548*4882a593Smuzhiyun fcpl = [] 549*4882a593Smuzhiyun for i in fcp: 550*4882a593Smuzhiyun if i == "..": 551*4882a593Smuzhiyun fcpl.pop() 552*4882a593Smuzhiyun else: 553*4882a593Smuzhiyun fcpl.append(i) 554*4882a593Smuzhiyun filetarget_path = "/".join(fcpl) 555*4882a593Smuzhiyun 556*4882a593Smuzhiyun try: 557*4882a593Smuzhiyun filetarget_obj = Target_File.objects.get(target = target_obj, path = filetarget_path) 558*4882a593Smuzhiyun except Target_File.DoesNotExist: 559*4882a593Smuzhiyun # we might have an invalid link; no way to detect this. just set it to None 560*4882a593Smuzhiyun filetarget_obj = None 561*4882a593Smuzhiyun 562*4882a593Smuzhiyun parent_obj = Target_File.objects.get(target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY) 563*4882a593Smuzhiyun 564*4882a593Smuzhiyun Target_File.objects.create( 565*4882a593Smuzhiyun target = target_obj, 566*4882a593Smuzhiyun path = path, 567*4882a593Smuzhiyun size = size, 568*4882a593Smuzhiyun inodetype = Target_File.ITYPE_SYMLINK, 569*4882a593Smuzhiyun permission = permission, 570*4882a593Smuzhiyun owner = user, 571*4882a593Smuzhiyun group = group, 572*4882a593Smuzhiyun directory = parent_obj, 573*4882a593Smuzhiyun sym_target = filetarget_obj) 574*4882a593Smuzhiyun 575*4882a593Smuzhiyun 576*4882a593Smuzhiyun def save_target_package_information(self, build_obj, target_obj, packagedict, pkgpnmap, recipes, built_package=False): 577*4882a593Smuzhiyun assert isinstance(build_obj, Build) 578*4882a593Smuzhiyun assert isinstance(target_obj, Target) 579*4882a593Smuzhiyun 580*4882a593Smuzhiyun errormsg = [] 581*4882a593Smuzhiyun for p in packagedict: 582*4882a593Smuzhiyun # Search name swtiches round the installed name vs package name 583*4882a593Smuzhiyun # by default installed name == package name 584*4882a593Smuzhiyun searchname = p 585*4882a593Smuzhiyun if p not in pkgpnmap: 586*4882a593Smuzhiyun logger.warning("Image packages list contains %p, but is" 587*4882a593Smuzhiyun " missing from all packages list where the" 588*4882a593Smuzhiyun " metadata comes from. Skipping...", p) 589*4882a593Smuzhiyun continue 590*4882a593Smuzhiyun 591*4882a593Smuzhiyun if 'OPKGN' in pkgpnmap[p].keys(): 592*4882a593Smuzhiyun searchname = pkgpnmap[p]['OPKGN'] 593*4882a593Smuzhiyun 594*4882a593Smuzhiyun built_recipe = recipes[pkgpnmap[p]['PN']] 595*4882a593Smuzhiyun 596*4882a593Smuzhiyun if built_package: 597*4882a593Smuzhiyun packagedict[p]['object'], created = Package.objects.get_or_create( build = build_obj, name = searchname ) 598*4882a593Smuzhiyun recipe = built_recipe 599*4882a593Smuzhiyun else: 600*4882a593Smuzhiyun packagedict[p]['object'], created = \ 601*4882a593Smuzhiyun CustomImagePackage.objects.get_or_create(name=searchname) 602*4882a593Smuzhiyun # Clear the Package_Dependency objects as we're going to update 603*4882a593Smuzhiyun # the CustomImagePackage with the latest dependency information 604*4882a593Smuzhiyun packagedict[p]['object'].package_dependencies_target.all().delete() 605*4882a593Smuzhiyun packagedict[p]['object'].package_dependencies_source.all().delete() 606*4882a593Smuzhiyun try: 607*4882a593Smuzhiyun recipe = self._cached_get( 608*4882a593Smuzhiyun Recipe, 609*4882a593Smuzhiyun name=built_recipe.name, 610*4882a593Smuzhiyun layer_version__build=None, 611*4882a593Smuzhiyun layer_version__release= 612*4882a593Smuzhiyun built_recipe.layer_version.release, 613*4882a593Smuzhiyun file_path=built_recipe.file_path, 614*4882a593Smuzhiyun version=built_recipe.version 615*4882a593Smuzhiyun ) 616*4882a593Smuzhiyun except (Recipe.DoesNotExist, 617*4882a593Smuzhiyun Recipe.MultipleObjectsReturned) as e: 618*4882a593Smuzhiyun logger.info("We did not find one recipe for the" 619*4882a593Smuzhiyun "configuration data package %s %s" % (p, e)) 620*4882a593Smuzhiyun continue 621*4882a593Smuzhiyun 622*4882a593Smuzhiyun if created or packagedict[p]['object'].size == -1: # save the data anyway we can, not just if it was not created here; bug [YOCTO #6887] 623*4882a593Smuzhiyun # fill in everything we can from the runtime-reverse package data 624*4882a593Smuzhiyun try: 625*4882a593Smuzhiyun packagedict[p]['object'].recipe = recipe 626*4882a593Smuzhiyun packagedict[p]['object'].version = pkgpnmap[p]['PV'] 627*4882a593Smuzhiyun packagedict[p]['object'].installed_name = p 628*4882a593Smuzhiyun packagedict[p]['object'].revision = pkgpnmap[p]['PR'] 629*4882a593Smuzhiyun packagedict[p]['object'].license = pkgpnmap[p]['LICENSE'] 630*4882a593Smuzhiyun packagedict[p]['object'].section = pkgpnmap[p]['SECTION'] 631*4882a593Smuzhiyun packagedict[p]['object'].summary = pkgpnmap[p]['SUMMARY'] 632*4882a593Smuzhiyun packagedict[p]['object'].description = pkgpnmap[p]['DESCRIPTION'] 633*4882a593Smuzhiyun packagedict[p]['object'].size = int(pkgpnmap[p]['PKGSIZE']) 634*4882a593Smuzhiyun 635*4882a593Smuzhiyun # no files recorded for this package, so save files info 636*4882a593Smuzhiyun packagefile_objects = [] 637*4882a593Smuzhiyun for targetpath in pkgpnmap[p]['FILES_INFO']: 638*4882a593Smuzhiyun targetfilesize = pkgpnmap[p]['FILES_INFO'][targetpath] 639*4882a593Smuzhiyun packagefile_objects.append(Package_File( package = packagedict[p]['object'], 640*4882a593Smuzhiyun path = targetpath, 641*4882a593Smuzhiyun size = targetfilesize)) 642*4882a593Smuzhiyun if packagefile_objects: 643*4882a593Smuzhiyun Package_File.objects.bulk_create(packagefile_objects) 644*4882a593Smuzhiyun except KeyError as e: 645*4882a593Smuzhiyun errormsg.append(" stpi: Key error, package %s key %s \n" % (p, e)) 646*4882a593Smuzhiyun 647*4882a593Smuzhiyun # save disk installed size 648*4882a593Smuzhiyun packagedict[p]['object'].installed_size = packagedict[p]['size'] 649*4882a593Smuzhiyun packagedict[p]['object'].save() 650*4882a593Smuzhiyun 651*4882a593Smuzhiyun if built_package: 652*4882a593Smuzhiyun Target_Installed_Package.objects.create(target = target_obj, package = packagedict[p]['object']) 653*4882a593Smuzhiyun 654*4882a593Smuzhiyun packagedeps_objs = [] 655*4882a593Smuzhiyun pattern_so = re.compile(r'.*\.so(\.\d*)?$') 656*4882a593Smuzhiyun pattern_lib = re.compile(r'.*\-suffix(\d*)?$') 657*4882a593Smuzhiyun pattern_ko = re.compile(r'^kernel-module-.*') 658*4882a593Smuzhiyun for p in packagedict: 659*4882a593Smuzhiyun for (px,deptype) in packagedict[p]['depends']: 660*4882a593Smuzhiyun if deptype == 'depends': 661*4882a593Smuzhiyun tdeptype = Package_Dependency.TYPE_TRDEPENDS 662*4882a593Smuzhiyun elif deptype == 'recommends': 663*4882a593Smuzhiyun tdeptype = Package_Dependency.TYPE_TRECOMMENDS 664*4882a593Smuzhiyun 665*4882a593Smuzhiyun try: 666*4882a593Smuzhiyun # Skip known non-package objects like libraries and kernel modules 667*4882a593Smuzhiyun if pattern_so.match(px) or pattern_lib.match(px): 668*4882a593Smuzhiyun logger.info("Toaster does not add library file dependencies to packages (%s,%s)", p, px) 669*4882a593Smuzhiyun continue 670*4882a593Smuzhiyun if pattern_ko.match(px): 671*4882a593Smuzhiyun logger.info("Toaster does not add kernel module dependencies to packages (%s,%s)", p, px) 672*4882a593Smuzhiyun continue 673*4882a593Smuzhiyun packagedeps_objs.append(Package_Dependency( 674*4882a593Smuzhiyun package = packagedict[p]['object'], 675*4882a593Smuzhiyun depends_on = packagedict[px]['object'], 676*4882a593Smuzhiyun dep_type = tdeptype, 677*4882a593Smuzhiyun target = target_obj)) 678*4882a593Smuzhiyun except KeyError as e: 679*4882a593Smuzhiyun logger.warning("Could not add dependency to the package %s " 680*4882a593Smuzhiyun "because %s is an unknown package", p, px) 681*4882a593Smuzhiyun 682*4882a593Smuzhiyun if packagedeps_objs: 683*4882a593Smuzhiyun Package_Dependency.objects.bulk_create(packagedeps_objs) 684*4882a593Smuzhiyun else: 685*4882a593Smuzhiyun logger.info("No package dependencies created") 686*4882a593Smuzhiyun 687*4882a593Smuzhiyun if errormsg: 688*4882a593Smuzhiyun logger.warning("buildinfohelper: target_package_info could not identify recipes: \n%s", "".join(errormsg)) 689*4882a593Smuzhiyun 690*4882a593Smuzhiyun def save_target_image_file_information(self, target_obj, file_name, file_size): 691*4882a593Smuzhiyun Target_Image_File.objects.create(target=target_obj, 692*4882a593Smuzhiyun file_name=file_name, file_size=file_size) 693*4882a593Smuzhiyun 694*4882a593Smuzhiyun def save_target_kernel_file(self, target_obj, file_name, file_size): 695*4882a593Smuzhiyun """ 696*4882a593Smuzhiyun Save kernel file (bzImage, modules*) information for a Target target_obj. 697*4882a593Smuzhiyun """ 698*4882a593Smuzhiyun TargetKernelFile.objects.create(target=target_obj, 699*4882a593Smuzhiyun file_name=file_name, file_size=file_size) 700*4882a593Smuzhiyun 701*4882a593Smuzhiyun def save_target_sdk_file(self, target_obj, file_name, file_size): 702*4882a593Smuzhiyun """ 703*4882a593Smuzhiyun Save SDK artifacts to the database, associating them with a 704*4882a593Smuzhiyun Target object. 705*4882a593Smuzhiyun """ 706*4882a593Smuzhiyun TargetSDKFile.objects.create(target=target_obj, file_name=file_name, 707*4882a593Smuzhiyun file_size=file_size) 708*4882a593Smuzhiyun 709*4882a593Smuzhiyun def create_logmessage(self, log_information): 710*4882a593Smuzhiyun assert 'build' in log_information 711*4882a593Smuzhiyun assert 'level' in log_information 712*4882a593Smuzhiyun assert 'message' in log_information 713*4882a593Smuzhiyun 714*4882a593Smuzhiyun log_object = LogMessage.objects.create( 715*4882a593Smuzhiyun build = log_information['build'], 716*4882a593Smuzhiyun level = log_information['level'], 717*4882a593Smuzhiyun message = log_information['message']) 718*4882a593Smuzhiyun 719*4882a593Smuzhiyun for v in vars(log_object): 720*4882a593Smuzhiyun if v in log_information.keys(): 721*4882a593Smuzhiyun vars(log_object)[v] = log_information[v] 722*4882a593Smuzhiyun 723*4882a593Smuzhiyun return log_object.save() 724*4882a593Smuzhiyun 725*4882a593Smuzhiyun 726*4882a593Smuzhiyun def save_build_package_information(self, build_obj, package_info, recipes, 727*4882a593Smuzhiyun built_package): 728*4882a593Smuzhiyun # assert isinstance(build_obj, Build) 729*4882a593Smuzhiyun 730*4882a593Smuzhiyun if not 'PN' in package_info.keys(): 731*4882a593Smuzhiyun # no package data to save (e.g. 'OPKGN'="lib64-*"|"lib32-*") 732*4882a593Smuzhiyun return None 733*4882a593Smuzhiyun 734*4882a593Smuzhiyun # create and save the object 735*4882a593Smuzhiyun pname = package_info['PKG'] 736*4882a593Smuzhiyun built_recipe = recipes[package_info['PN']] 737*4882a593Smuzhiyun if 'OPKGN' in package_info.keys(): 738*4882a593Smuzhiyun pname = package_info['OPKGN'] 739*4882a593Smuzhiyun 740*4882a593Smuzhiyun if built_package: 741*4882a593Smuzhiyun bp_object, _ = Package.objects.get_or_create( build = build_obj, 742*4882a593Smuzhiyun name = pname ) 743*4882a593Smuzhiyun recipe = built_recipe 744*4882a593Smuzhiyun else: 745*4882a593Smuzhiyun bp_object, created = \ 746*4882a593Smuzhiyun CustomImagePackage.objects.get_or_create(name=pname) 747*4882a593Smuzhiyun try: 748*4882a593Smuzhiyun recipe = self._cached_get(Recipe, 749*4882a593Smuzhiyun name=built_recipe.name, 750*4882a593Smuzhiyun layer_version__build=None, 751*4882a593Smuzhiyun file_path=built_recipe.file_path, 752*4882a593Smuzhiyun version=built_recipe.version) 753*4882a593Smuzhiyun 754*4882a593Smuzhiyun except (Recipe.DoesNotExist, Recipe.MultipleObjectsReturned): 755*4882a593Smuzhiyun logger.debug("We did not find one recipe for the configuration" 756*4882a593Smuzhiyun "data package %s" % pname) 757*4882a593Smuzhiyun return 758*4882a593Smuzhiyun 759*4882a593Smuzhiyun bp_object.installed_name = package_info['PKG'] 760*4882a593Smuzhiyun bp_object.recipe = recipe 761*4882a593Smuzhiyun bp_object.version = package_info['PKGV'] 762*4882a593Smuzhiyun bp_object.revision = package_info['PKGR'] 763*4882a593Smuzhiyun bp_object.summary = package_info['SUMMARY'] 764*4882a593Smuzhiyun bp_object.description = package_info['DESCRIPTION'] 765*4882a593Smuzhiyun bp_object.size = int(package_info['PKGSIZE']) 766*4882a593Smuzhiyun bp_object.section = package_info['SECTION'] 767*4882a593Smuzhiyun bp_object.license = package_info['LICENSE'] 768*4882a593Smuzhiyun bp_object.save() 769*4882a593Smuzhiyun 770*4882a593Smuzhiyun # save any attached file information 771*4882a593Smuzhiyun packagefile_objects = [] 772*4882a593Smuzhiyun for path in package_info['FILES_INFO']: 773*4882a593Smuzhiyun packagefile_objects.append(Package_File( package = bp_object, 774*4882a593Smuzhiyun path = path, 775*4882a593Smuzhiyun size = package_info['FILES_INFO'][path] )) 776*4882a593Smuzhiyun if packagefile_objects: 777*4882a593Smuzhiyun Package_File.objects.bulk_create(packagefile_objects) 778*4882a593Smuzhiyun 779*4882a593Smuzhiyun def _po_byname(p): 780*4882a593Smuzhiyun if built_package: 781*4882a593Smuzhiyun pkg, created = Package.objects.get_or_create(build=build_obj, 782*4882a593Smuzhiyun name=p) 783*4882a593Smuzhiyun else: 784*4882a593Smuzhiyun pkg, created = CustomImagePackage.objects.get_or_create(name=p) 785*4882a593Smuzhiyun 786*4882a593Smuzhiyun if created: 787*4882a593Smuzhiyun pkg.size = -1 788*4882a593Smuzhiyun pkg.save() 789*4882a593Smuzhiyun return pkg 790*4882a593Smuzhiyun 791*4882a593Smuzhiyun packagedeps_objs = [] 792*4882a593Smuzhiyun # save soft dependency information 793*4882a593Smuzhiyun if 'RDEPENDS' in package_info and package_info['RDEPENDS']: 794*4882a593Smuzhiyun for p in bb.utils.explode_deps(package_info['RDEPENDS']): 795*4882a593Smuzhiyun packagedeps_objs.append(Package_Dependency( package = bp_object, 796*4882a593Smuzhiyun depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RDEPENDS)) 797*4882a593Smuzhiyun if 'RPROVIDES' in package_info and package_info['RPROVIDES']: 798*4882a593Smuzhiyun for p in bb.utils.explode_deps(package_info['RPROVIDES']): 799*4882a593Smuzhiyun packagedeps_objs.append(Package_Dependency( package = bp_object, 800*4882a593Smuzhiyun depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RPROVIDES)) 801*4882a593Smuzhiyun if 'RRECOMMENDS' in package_info and package_info['RRECOMMENDS']: 802*4882a593Smuzhiyun for p in bb.utils.explode_deps(package_info['RRECOMMENDS']): 803*4882a593Smuzhiyun packagedeps_objs.append(Package_Dependency( package = bp_object, 804*4882a593Smuzhiyun depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RRECOMMENDS)) 805*4882a593Smuzhiyun if 'RSUGGESTS' in package_info and package_info['RSUGGESTS']: 806*4882a593Smuzhiyun for p in bb.utils.explode_deps(package_info['RSUGGESTS']): 807*4882a593Smuzhiyun packagedeps_objs.append(Package_Dependency( package = bp_object, 808*4882a593Smuzhiyun depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RSUGGESTS)) 809*4882a593Smuzhiyun if 'RREPLACES' in package_info and package_info['RREPLACES']: 810*4882a593Smuzhiyun for p in bb.utils.explode_deps(package_info['RREPLACES']): 811*4882a593Smuzhiyun packagedeps_objs.append(Package_Dependency( package = bp_object, 812*4882a593Smuzhiyun depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RREPLACES)) 813*4882a593Smuzhiyun if 'RCONFLICTS' in package_info and package_info['RCONFLICTS']: 814*4882a593Smuzhiyun for p in bb.utils.explode_deps(package_info['RCONFLICTS']): 815*4882a593Smuzhiyun packagedeps_objs.append(Package_Dependency( package = bp_object, 816*4882a593Smuzhiyun depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RCONFLICTS)) 817*4882a593Smuzhiyun 818*4882a593Smuzhiyun if packagedeps_objs: 819*4882a593Smuzhiyun Package_Dependency.objects.bulk_create(packagedeps_objs) 820*4882a593Smuzhiyun 821*4882a593Smuzhiyun return bp_object 822*4882a593Smuzhiyun 823*4882a593Smuzhiyun def save_build_variables(self, build_obj, vardump): 824*4882a593Smuzhiyun assert isinstance(build_obj, Build) 825*4882a593Smuzhiyun 826*4882a593Smuzhiyun for k in vardump: 827*4882a593Smuzhiyun desc = vardump[k]['doc'] 828*4882a593Smuzhiyun if desc is None: 829*4882a593Smuzhiyun var_words = [word for word in k.split('_')] 830*4882a593Smuzhiyun root_var = "_".join([word for word in var_words if word.isupper()]) 831*4882a593Smuzhiyun if root_var and root_var != k and root_var in vardump: 832*4882a593Smuzhiyun desc = vardump[root_var]['doc'] 833*4882a593Smuzhiyun if desc is None: 834*4882a593Smuzhiyun desc = '' 835*4882a593Smuzhiyun if desc: 836*4882a593Smuzhiyun HelpText.objects.get_or_create(build=build_obj, 837*4882a593Smuzhiyun area=HelpText.VARIABLE, 838*4882a593Smuzhiyun key=k, text=desc) 839*4882a593Smuzhiyun if not bool(vardump[k]['func']): 840*4882a593Smuzhiyun value = vardump[k]['v'] 841*4882a593Smuzhiyun if value is None: 842*4882a593Smuzhiyun value = '' 843*4882a593Smuzhiyun variable_obj = Variable.objects.create( build = build_obj, 844*4882a593Smuzhiyun variable_name = k, 845*4882a593Smuzhiyun variable_value = value, 846*4882a593Smuzhiyun description = desc) 847*4882a593Smuzhiyun 848*4882a593Smuzhiyun varhist_objects = [] 849*4882a593Smuzhiyun for vh in vardump[k]['history']: 850*4882a593Smuzhiyun if not 'documentation.conf' in vh['file']: 851*4882a593Smuzhiyun varhist_objects.append(VariableHistory( variable = variable_obj, 852*4882a593Smuzhiyun file_name = vh['file'], 853*4882a593Smuzhiyun line_number = vh['line'], 854*4882a593Smuzhiyun operation = vh['op'])) 855*4882a593Smuzhiyun if varhist_objects: 856*4882a593Smuzhiyun VariableHistory.objects.bulk_create(varhist_objects) 857*4882a593Smuzhiyun 858*4882a593Smuzhiyun 859*4882a593Smuzhiyunclass MockEvent(object): 860*4882a593Smuzhiyun """ This object is used to create event, for which normal event-processing methods can 861*4882a593Smuzhiyun be used, out of data that is not coming via an actual event 862*4882a593Smuzhiyun """ 863*4882a593Smuzhiyun def __init__(self): 864*4882a593Smuzhiyun self.msg = None 865*4882a593Smuzhiyun self.levelno = None 866*4882a593Smuzhiyun self.taskname = None 867*4882a593Smuzhiyun self.taskhash = None 868*4882a593Smuzhiyun self.pathname = None 869*4882a593Smuzhiyun self.lineno = None 870*4882a593Smuzhiyun 871*4882a593Smuzhiyun def getMessage(self): 872*4882a593Smuzhiyun """ 873*4882a593Smuzhiyun Simulate LogRecord message return 874*4882a593Smuzhiyun """ 875*4882a593Smuzhiyun return self.msg 876*4882a593Smuzhiyun 877*4882a593Smuzhiyun 878*4882a593Smuzhiyunclass BuildInfoHelper(object): 879*4882a593Smuzhiyun """ This class gathers the build information from the server and sends it 880*4882a593Smuzhiyun towards the ORM wrapper for storing in the database 881*4882a593Smuzhiyun It is instantiated once per build 882*4882a593Smuzhiyun Keeps in memory all data that needs matching before writing it to the database 883*4882a593Smuzhiyun """ 884*4882a593Smuzhiyun 885*4882a593Smuzhiyun # tasks which produce image files; note we include '', as we set 886*4882a593Smuzhiyun # the task for a target to '' (i.e. 'build') if no target is 887*4882a593Smuzhiyun # explicitly defined 888*4882a593Smuzhiyun IMAGE_GENERATING_TASKS = ['', 'build', 'image', 'populate_sdk_ext'] 889*4882a593Smuzhiyun 890*4882a593Smuzhiyun # pylint: disable=protected-access 891*4882a593Smuzhiyun # the code will look into the protected variables of the event; no easy way around this 892*4882a593Smuzhiyun # pylint: disable=bad-continuation 893*4882a593Smuzhiyun # we do not follow the python conventions for continuation indentation due to long lines here 894*4882a593Smuzhiyun 895*4882a593Smuzhiyun def __init__(self, server, has_build_history = False, brbe = None): 896*4882a593Smuzhiyun self.internal_state = {} 897*4882a593Smuzhiyun self.internal_state['taskdata'] = {} 898*4882a593Smuzhiyun self.internal_state['targets'] = [] 899*4882a593Smuzhiyun self.task_order = 0 900*4882a593Smuzhiyun self.autocommit_step = 1 901*4882a593Smuzhiyun self.server = server 902*4882a593Smuzhiyun self.orm_wrapper = ORMWrapper() 903*4882a593Smuzhiyun self.has_build_history = has_build_history 904*4882a593Smuzhiyun self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0] 905*4882a593Smuzhiyun 906*4882a593Smuzhiyun # this is set for Toaster-triggered builds by localhostbecontroller 907*4882a593Smuzhiyun # via toasterui 908*4882a593Smuzhiyun self.brbe = brbe 909*4882a593Smuzhiyun 910*4882a593Smuzhiyun self.project = None 911*4882a593Smuzhiyun 912*4882a593Smuzhiyun logger.debug("buildinfohelper: Build info helper inited %s" % vars(self)) 913*4882a593Smuzhiyun 914*4882a593Smuzhiyun 915*4882a593Smuzhiyun ################### 916*4882a593Smuzhiyun ## methods to convert event/external info into objects that the ORM layer uses 917*4882a593Smuzhiyun 918*4882a593Smuzhiyun def _ensure_build(self): 919*4882a593Smuzhiyun """ 920*4882a593Smuzhiyun Ensure the current build object exists and is up to date with 921*4882a593Smuzhiyun data on the bitbake server 922*4882a593Smuzhiyun """ 923*4882a593Smuzhiyun if not 'build' in self.internal_state or not self.internal_state['build']: 924*4882a593Smuzhiyun # create the Build object 925*4882a593Smuzhiyun self.internal_state['build'] = \ 926*4882a593Smuzhiyun self.orm_wrapper.get_or_create_build_object(self.brbe) 927*4882a593Smuzhiyun 928*4882a593Smuzhiyun build = self.internal_state['build'] 929*4882a593Smuzhiyun 930*4882a593Smuzhiyun # update missing fields on the Build object with found data 931*4882a593Smuzhiyun build_info = {} 932*4882a593Smuzhiyun 933*4882a593Smuzhiyun # set to True if at least one field is going to be set 934*4882a593Smuzhiyun changed = False 935*4882a593Smuzhiyun 936*4882a593Smuzhiyun if not build.build_name: 937*4882a593Smuzhiyun build_name = self.server.runCommand(["getVariable", "BUILDNAME"])[0] 938*4882a593Smuzhiyun 939*4882a593Smuzhiyun # only reset the build name if the one on the server is actually 940*4882a593Smuzhiyun # a valid value for the build_name field 941*4882a593Smuzhiyun if build_name is not None: 942*4882a593Smuzhiyun build_info['build_name'] = build_name 943*4882a593Smuzhiyun changed = True 944*4882a593Smuzhiyun 945*4882a593Smuzhiyun if not build.machine: 946*4882a593Smuzhiyun build_info['machine'] = self.server.runCommand(["getVariable", "MACHINE"])[0] 947*4882a593Smuzhiyun changed = True 948*4882a593Smuzhiyun 949*4882a593Smuzhiyun if not build.distro: 950*4882a593Smuzhiyun build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0] 951*4882a593Smuzhiyun changed = True 952*4882a593Smuzhiyun 953*4882a593Smuzhiyun if not build.distro_version: 954*4882a593Smuzhiyun build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0] 955*4882a593Smuzhiyun changed = True 956*4882a593Smuzhiyun 957*4882a593Smuzhiyun if not build.bitbake_version: 958*4882a593Smuzhiyun build_info['bitbake_version'] = self.server.runCommand(["getVariable", "BB_VERSION"])[0] 959*4882a593Smuzhiyun changed = True 960*4882a593Smuzhiyun 961*4882a593Smuzhiyun if changed: 962*4882a593Smuzhiyun self.orm_wrapper.update_build(self.internal_state['build'], build_info) 963*4882a593Smuzhiyun 964*4882a593Smuzhiyun def _get_task_information(self, event, recipe): 965*4882a593Smuzhiyun assert 'taskname' in vars(event) 966*4882a593Smuzhiyun self._ensure_build() 967*4882a593Smuzhiyun 968*4882a593Smuzhiyun task_information = {} 969*4882a593Smuzhiyun task_information['build'] = self.internal_state['build'] 970*4882a593Smuzhiyun task_information['outcome'] = Task.OUTCOME_NA 971*4882a593Smuzhiyun task_information['recipe'] = recipe 972*4882a593Smuzhiyun task_information['task_name'] = event.taskname 973*4882a593Smuzhiyun try: 974*4882a593Smuzhiyun # some tasks don't come with a hash. and that's ok 975*4882a593Smuzhiyun task_information['sstate_checksum'] = event.taskhash 976*4882a593Smuzhiyun except AttributeError: 977*4882a593Smuzhiyun pass 978*4882a593Smuzhiyun return task_information 979*4882a593Smuzhiyun 980*4882a593Smuzhiyun def _get_layer_version_for_dependency(self, pathRE): 981*4882a593Smuzhiyun """ Returns the layer in the toaster db that has a full regex 982*4882a593Smuzhiyun match to the pathRE. pathRE - the layer path passed as a regex in the 983*4882a593Smuzhiyun event. It is created in cooker.py as a collection for the layer 984*4882a593Smuzhiyun priorities. 985*4882a593Smuzhiyun """ 986*4882a593Smuzhiyun self._ensure_build() 987*4882a593Smuzhiyun 988*4882a593Smuzhiyun def _sort_longest_path(layer_version): 989*4882a593Smuzhiyun assert isinstance(layer_version, Layer_Version) 990*4882a593Smuzhiyun return len(layer_version.local_path) 991*4882a593Smuzhiyun 992*4882a593Smuzhiyun # Our paths don't append a trailing slash 993*4882a593Smuzhiyun if pathRE.endswith("/"): 994*4882a593Smuzhiyun pathRE = pathRE[:-1] 995*4882a593Smuzhiyun 996*4882a593Smuzhiyun p = re.compile(pathRE) 997*4882a593Smuzhiyun path=re.sub(r'[$^]',r'',pathRE) 998*4882a593Smuzhiyun # Heuristics: we always match recipe to the deepest layer path in 999*4882a593Smuzhiyun # the discovered layers 1000*4882a593Smuzhiyun for lvo in sorted(self.orm_wrapper.layer_version_objects, 1001*4882a593Smuzhiyun reverse=True, key=_sort_longest_path): 1002*4882a593Smuzhiyun if p.fullmatch(os.path.abspath(lvo.local_path)): 1003*4882a593Smuzhiyun return lvo 1004*4882a593Smuzhiyun if lvo.layer.local_source_dir: 1005*4882a593Smuzhiyun if p.fullmatch(os.path.abspath(lvo.layer.local_source_dir)): 1006*4882a593Smuzhiyun return lvo 1007*4882a593Smuzhiyun if 0 == path.find(lvo.local_path): 1008*4882a593Smuzhiyun # sub-layer path inside existing layer 1009*4882a593Smuzhiyun return lvo 1010*4882a593Smuzhiyun 1011*4882a593Smuzhiyun # if we get here, we didn't read layers correctly; 1012*4882a593Smuzhiyun # dump whatever information we have on the error log 1013*4882a593Smuzhiyun logger.warning("Could not match layer dependency for path %s : %s", 1014*4882a593Smuzhiyun pathRE, 1015*4882a593Smuzhiyun self.orm_wrapper.layer_version_objects) 1016*4882a593Smuzhiyun return None 1017*4882a593Smuzhiyun 1018*4882a593Smuzhiyun def _get_layer_version_for_path(self, path): 1019*4882a593Smuzhiyun self._ensure_build() 1020*4882a593Smuzhiyun 1021*4882a593Smuzhiyun def _slkey_interactive(layer_version): 1022*4882a593Smuzhiyun assert isinstance(layer_version, Layer_Version) 1023*4882a593Smuzhiyun return len(layer_version.local_path) 1024*4882a593Smuzhiyun 1025*4882a593Smuzhiyun # Heuristics: we always match recipe to the deepest layer path in the discovered layers 1026*4882a593Smuzhiyun for lvo in sorted(self.orm_wrapper.layer_version_objects, reverse=True, key=_slkey_interactive): 1027*4882a593Smuzhiyun # we can match to the recipe file path 1028*4882a593Smuzhiyun if path.startswith(lvo.local_path): 1029*4882a593Smuzhiyun return lvo 1030*4882a593Smuzhiyun if lvo.layer.local_source_dir and \ 1031*4882a593Smuzhiyun path.startswith(lvo.layer.local_source_dir): 1032*4882a593Smuzhiyun return lvo 1033*4882a593Smuzhiyun 1034*4882a593Smuzhiyun #if we get here, we didn't read layers correctly; dump whatever information we have on the error log 1035*4882a593Smuzhiyun logger.warning("Could not match layer version for recipe path %s : %s", path, self.orm_wrapper.layer_version_objects) 1036*4882a593Smuzhiyun 1037*4882a593Smuzhiyun #mockup the new layer 1038*4882a593Smuzhiyun unknown_layer, _ = Layer.objects.get_or_create(name="Unidentified layer", layer_index_url="") 1039*4882a593Smuzhiyun unknown_layer_version_obj, _ = Layer_Version.objects.get_or_create(layer = unknown_layer, build = self.internal_state['build']) 1040*4882a593Smuzhiyun 1041*4882a593Smuzhiyun # append it so we don't run into this error again and again 1042*4882a593Smuzhiyun self.orm_wrapper.layer_version_objects.append(unknown_layer_version_obj) 1043*4882a593Smuzhiyun 1044*4882a593Smuzhiyun return unknown_layer_version_obj 1045*4882a593Smuzhiyun 1046*4882a593Smuzhiyun def _get_recipe_information_from_taskfile(self, taskfile): 1047*4882a593Smuzhiyun localfilepath = taskfile.split(":")[-1] 1048*4882a593Smuzhiyun filepath_flags = ":".join(sorted(taskfile.split(":")[:-1])) 1049*4882a593Smuzhiyun layer_version_obj = self._get_layer_version_for_path(localfilepath) 1050*4882a593Smuzhiyun 1051*4882a593Smuzhiyun 1052*4882a593Smuzhiyun 1053*4882a593Smuzhiyun recipe_info = {} 1054*4882a593Smuzhiyun recipe_info['layer_version'] = layer_version_obj 1055*4882a593Smuzhiyun recipe_info['file_path'] = localfilepath 1056*4882a593Smuzhiyun recipe_info['pathflags'] = filepath_flags 1057*4882a593Smuzhiyun 1058*4882a593Smuzhiyun if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path): 1059*4882a593Smuzhiyun recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/") 1060*4882a593Smuzhiyun else: 1061*4882a593Smuzhiyun raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path)) 1062*4882a593Smuzhiyun 1063*4882a593Smuzhiyun return recipe_info 1064*4882a593Smuzhiyun 1065*4882a593Smuzhiyun 1066*4882a593Smuzhiyun ################################ 1067*4882a593Smuzhiyun ## external available methods to store information 1068*4882a593Smuzhiyun @staticmethod 1069*4882a593Smuzhiyun def _get_data_from_event(event): 1070*4882a593Smuzhiyun evdata = None 1071*4882a593Smuzhiyun if '_localdata' in vars(event): 1072*4882a593Smuzhiyun evdata = event._localdata 1073*4882a593Smuzhiyun elif 'data' in vars(event): 1074*4882a593Smuzhiyun evdata = event.data 1075*4882a593Smuzhiyun else: 1076*4882a593Smuzhiyun raise Exception("Event with neither _localdata or data properties") 1077*4882a593Smuzhiyun return evdata 1078*4882a593Smuzhiyun 1079*4882a593Smuzhiyun def store_layer_info(self, event): 1080*4882a593Smuzhiyun layerinfos = BuildInfoHelper._get_data_from_event(event) 1081*4882a593Smuzhiyun self.internal_state['lvs'] = {} 1082*4882a593Smuzhiyun for layer in layerinfos: 1083*4882a593Smuzhiyun try: 1084*4882a593Smuzhiyun self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)] = layerinfos[layer]['version'] 1085*4882a593Smuzhiyun self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)]['local_path'] = layerinfos[layer]['local_path'] 1086*4882a593Smuzhiyun except NotExisting as nee: 1087*4882a593Smuzhiyun logger.warning("buildinfohelper: cannot identify layer exception:%s ", nee) 1088*4882a593Smuzhiyun 1089*4882a593Smuzhiyun def store_started_build(self): 1090*4882a593Smuzhiyun self._ensure_build() 1091*4882a593Smuzhiyun 1092*4882a593Smuzhiyun def save_build_log_file_path(self, build_log_path): 1093*4882a593Smuzhiyun self._ensure_build() 1094*4882a593Smuzhiyun 1095*4882a593Smuzhiyun if not self.internal_state['build'].cooker_log_path: 1096*4882a593Smuzhiyun data_dict = {'cooker_log_path': build_log_path} 1097*4882a593Smuzhiyun self.orm_wrapper.update_build(self.internal_state['build'], data_dict) 1098*4882a593Smuzhiyun 1099*4882a593Smuzhiyun def save_build_targets(self, event): 1100*4882a593Smuzhiyun self._ensure_build() 1101*4882a593Smuzhiyun 1102*4882a593Smuzhiyun # create target information 1103*4882a593Smuzhiyun assert '_pkgs' in vars(event) 1104*4882a593Smuzhiyun target_information = {} 1105*4882a593Smuzhiyun target_information['targets'] = event._pkgs 1106*4882a593Smuzhiyun target_information['build'] = self.internal_state['build'] 1107*4882a593Smuzhiyun 1108*4882a593Smuzhiyun self.internal_state['targets'] = self.orm_wrapper.get_or_create_targets(target_information) 1109*4882a593Smuzhiyun 1110*4882a593Smuzhiyun def save_build_layers_and_variables(self): 1111*4882a593Smuzhiyun self._ensure_build() 1112*4882a593Smuzhiyun 1113*4882a593Smuzhiyun build_obj = self.internal_state['build'] 1114*4882a593Smuzhiyun 1115*4882a593Smuzhiyun # save layer version information for this build 1116*4882a593Smuzhiyun if not 'lvs' in self.internal_state: 1117*4882a593Smuzhiyun logger.error("Layer version information not found; Check if the bitbake server was configured to inherit toaster.bbclass.") 1118*4882a593Smuzhiyun else: 1119*4882a593Smuzhiyun for layer_obj in self.internal_state['lvs']: 1120*4882a593Smuzhiyun self.orm_wrapper.get_update_layer_version_object(build_obj, layer_obj, self.internal_state['lvs'][layer_obj]) 1121*4882a593Smuzhiyun 1122*4882a593Smuzhiyun del self.internal_state['lvs'] 1123*4882a593Smuzhiyun 1124*4882a593Smuzhiyun # Save build configuration 1125*4882a593Smuzhiyun data = self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0] 1126*4882a593Smuzhiyun 1127*4882a593Smuzhiyun # convert the paths from absolute to relative to either the build directory or layer checkouts 1128*4882a593Smuzhiyun path_prefixes = [] 1129*4882a593Smuzhiyun 1130*4882a593Smuzhiyun if self.brbe is not None: 1131*4882a593Smuzhiyun _, be_id = self.brbe.split(":") 1132*4882a593Smuzhiyun be = BuildEnvironment.objects.get(pk = be_id) 1133*4882a593Smuzhiyun path_prefixes.append(be.builddir) 1134*4882a593Smuzhiyun 1135*4882a593Smuzhiyun for layer in sorted(self.orm_wrapper.layer_version_objects, key = lambda x:len(x.local_path), reverse=True): 1136*4882a593Smuzhiyun path_prefixes.append(layer.local_path) 1137*4882a593Smuzhiyun 1138*4882a593Smuzhiyun # we strip the prefixes 1139*4882a593Smuzhiyun for k in data: 1140*4882a593Smuzhiyun if not bool(data[k]['func']): 1141*4882a593Smuzhiyun for vh in data[k]['history']: 1142*4882a593Smuzhiyun if not 'documentation.conf' in vh['file']: 1143*4882a593Smuzhiyun abs_file_name = vh['file'] 1144*4882a593Smuzhiyun for pp in path_prefixes: 1145*4882a593Smuzhiyun if abs_file_name.startswith(pp + "/"): 1146*4882a593Smuzhiyun # preserve layer name in relative path 1147*4882a593Smuzhiyun vh['file']=abs_file_name[pp.rfind("/")+1:] 1148*4882a593Smuzhiyun break 1149*4882a593Smuzhiyun 1150*4882a593Smuzhiyun # save the variables 1151*4882a593Smuzhiyun self.orm_wrapper.save_build_variables(build_obj, data) 1152*4882a593Smuzhiyun 1153*4882a593Smuzhiyun return self.brbe 1154*4882a593Smuzhiyun 1155*4882a593Smuzhiyun def set_recipes_to_parse(self, num_recipes): 1156*4882a593Smuzhiyun """ 1157*4882a593Smuzhiyun Set the number of recipes which need to be parsed for this build. 1158*4882a593Smuzhiyun This is set the first time ParseStarted is received by toasterui. 1159*4882a593Smuzhiyun """ 1160*4882a593Smuzhiyun self._ensure_build() 1161*4882a593Smuzhiyun self.internal_state['build'].recipes_to_parse = num_recipes 1162*4882a593Smuzhiyun self.internal_state['build'].save() 1163*4882a593Smuzhiyun 1164*4882a593Smuzhiyun def set_recipes_parsed(self, num_recipes): 1165*4882a593Smuzhiyun """ 1166*4882a593Smuzhiyun Set the number of recipes parsed so far for this build; this is updated 1167*4882a593Smuzhiyun each time a ParseProgress or ParseCompleted event is received by 1168*4882a593Smuzhiyun toasterui. 1169*4882a593Smuzhiyun """ 1170*4882a593Smuzhiyun self._ensure_build() 1171*4882a593Smuzhiyun if num_recipes <= self.internal_state['build'].recipes_to_parse: 1172*4882a593Smuzhiyun self.internal_state['build'].recipes_parsed = num_recipes 1173*4882a593Smuzhiyun self.internal_state['build'].save() 1174*4882a593Smuzhiyun 1175*4882a593Smuzhiyun def update_target_image_file(self, event): 1176*4882a593Smuzhiyun evdata = BuildInfoHelper._get_data_from_event(event) 1177*4882a593Smuzhiyun 1178*4882a593Smuzhiyun for t in self.internal_state['targets']: 1179*4882a593Smuzhiyun if t.is_image: 1180*4882a593Smuzhiyun output_files = list(evdata.keys()) 1181*4882a593Smuzhiyun for output in output_files: 1182*4882a593Smuzhiyun if t.target in output and 'rootfs' in output and not output.endswith(".manifest"): 1183*4882a593Smuzhiyun self.orm_wrapper.save_target_image_file_information(t, output, evdata[output]) 1184*4882a593Smuzhiyun 1185*4882a593Smuzhiyun def update_artifact_image_file(self, event): 1186*4882a593Smuzhiyun self._ensure_build() 1187*4882a593Smuzhiyun evdata = BuildInfoHelper._get_data_from_event(event) 1188*4882a593Smuzhiyun for artifact_path in evdata.keys(): 1189*4882a593Smuzhiyun self.orm_wrapper.save_artifact_information( 1190*4882a593Smuzhiyun self.internal_state['build'], artifact_path, 1191*4882a593Smuzhiyun evdata[artifact_path]) 1192*4882a593Smuzhiyun 1193*4882a593Smuzhiyun def update_build_information(self, event, errors, warnings, taskfailures): 1194*4882a593Smuzhiyun self._ensure_build() 1195*4882a593Smuzhiyun self.orm_wrapper.update_build_stats_and_outcome( 1196*4882a593Smuzhiyun self.internal_state['build'], errors, warnings, taskfailures) 1197*4882a593Smuzhiyun 1198*4882a593Smuzhiyun def store_started_task(self, event): 1199*4882a593Smuzhiyun assert isinstance(event, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped)) 1200*4882a593Smuzhiyun assert 'taskfile' in vars(event) 1201*4882a593Smuzhiyun localfilepath = event.taskfile.split(":")[-1] 1202*4882a593Smuzhiyun assert localfilepath.startswith("/") 1203*4882a593Smuzhiyun 1204*4882a593Smuzhiyun identifier = event.taskfile + ":" + event.taskname 1205*4882a593Smuzhiyun 1206*4882a593Smuzhiyun recipe_information = self._get_recipe_information_from_taskfile(event.taskfile) 1207*4882a593Smuzhiyun recipe = self.orm_wrapper.get_update_recipe_object(recipe_information, True) 1208*4882a593Smuzhiyun 1209*4882a593Smuzhiyun task_information = self._get_task_information(event, recipe) 1210*4882a593Smuzhiyun task_information['outcome'] = Task.OUTCOME_NA 1211*4882a593Smuzhiyun 1212*4882a593Smuzhiyun if isinstance(event, bb.runqueue.runQueueTaskSkipped): 1213*4882a593Smuzhiyun assert 'reason' in vars(event) 1214*4882a593Smuzhiyun task_information['task_executed'] = False 1215*4882a593Smuzhiyun if event.reason == "covered": 1216*4882a593Smuzhiyun task_information['outcome'] = Task.OUTCOME_COVERED 1217*4882a593Smuzhiyun if event.reason == "existing": 1218*4882a593Smuzhiyun task_information['outcome'] = Task.OUTCOME_PREBUILT 1219*4882a593Smuzhiyun else: 1220*4882a593Smuzhiyun task_information['task_executed'] = True 1221*4882a593Smuzhiyun if 'noexec' in vars(event) and event.noexec: 1222*4882a593Smuzhiyun task_information['task_executed'] = False 1223*4882a593Smuzhiyun task_information['outcome'] = Task.OUTCOME_EMPTY 1224*4882a593Smuzhiyun task_information['script_type'] = Task.CODING_NA 1225*4882a593Smuzhiyun 1226*4882a593Smuzhiyun # do not assign order numbers to scene tasks 1227*4882a593Smuzhiyun if not isinstance(event, bb.runqueue.sceneQueueTaskStarted): 1228*4882a593Smuzhiyun self.task_order += 1 1229*4882a593Smuzhiyun task_information['order'] = self.task_order 1230*4882a593Smuzhiyun 1231*4882a593Smuzhiyun self.orm_wrapper.get_update_task_object(task_information) 1232*4882a593Smuzhiyun 1233*4882a593Smuzhiyun self.internal_state['taskdata'][identifier] = { 1234*4882a593Smuzhiyun 'outcome': task_information['outcome'], 1235*4882a593Smuzhiyun } 1236*4882a593Smuzhiyun 1237*4882a593Smuzhiyun 1238*4882a593Smuzhiyun def store_tasks_stats(self, event): 1239*4882a593Smuzhiyun self._ensure_build() 1240*4882a593Smuzhiyun task_data = BuildInfoHelper._get_data_from_event(event) 1241*4882a593Smuzhiyun 1242*4882a593Smuzhiyun for (task_file, task_name, task_stats, recipe_name) in task_data: 1243*4882a593Smuzhiyun build = self.internal_state['build'] 1244*4882a593Smuzhiyun self.orm_wrapper.update_task_object(build, task_name, recipe_name, task_stats) 1245*4882a593Smuzhiyun 1246*4882a593Smuzhiyun def update_and_store_task(self, event): 1247*4882a593Smuzhiyun assert 'taskfile' in vars(event) 1248*4882a593Smuzhiyun localfilepath = event.taskfile.split(":")[-1] 1249*4882a593Smuzhiyun assert localfilepath.startswith("/") 1250*4882a593Smuzhiyun 1251*4882a593Smuzhiyun identifier = event.taskfile + ":" + event.taskname 1252*4882a593Smuzhiyun if not identifier in self.internal_state['taskdata']: 1253*4882a593Smuzhiyun if isinstance(event, bb.build.TaskBase): 1254*4882a593Smuzhiyun # we do a bit of guessing 1255*4882a593Smuzhiyun candidates = [x for x in self.internal_state['taskdata'].keys() if x.endswith(identifier)] 1256*4882a593Smuzhiyun if len(candidates) == 1: 1257*4882a593Smuzhiyun identifier = candidates[0] 1258*4882a593Smuzhiyun elif len(candidates) > 1 and hasattr(event,'_package'): 1259*4882a593Smuzhiyun if 'native-' in event._package: 1260*4882a593Smuzhiyun identifier = 'native:' + identifier 1261*4882a593Smuzhiyun if 'nativesdk-' in event._package: 1262*4882a593Smuzhiyun identifier = 'nativesdk:' + identifier 1263*4882a593Smuzhiyun candidates = [x for x in self.internal_state['taskdata'].keys() if x.endswith(identifier)] 1264*4882a593Smuzhiyun if len(candidates) == 1: 1265*4882a593Smuzhiyun identifier = candidates[0] 1266*4882a593Smuzhiyun 1267*4882a593Smuzhiyun assert identifier in self.internal_state['taskdata'] 1268*4882a593Smuzhiyun identifierlist = identifier.split(":") 1269*4882a593Smuzhiyun realtaskfile = ":".join(identifierlist[0:len(identifierlist)-1]) 1270*4882a593Smuzhiyun recipe_information = self._get_recipe_information_from_taskfile(realtaskfile) 1271*4882a593Smuzhiyun recipe = self.orm_wrapper.get_update_recipe_object(recipe_information, True) 1272*4882a593Smuzhiyun task_information = self._get_task_information(event,recipe) 1273*4882a593Smuzhiyun 1274*4882a593Smuzhiyun task_information['outcome'] = self.internal_state['taskdata'][identifier]['outcome'] 1275*4882a593Smuzhiyun 1276*4882a593Smuzhiyun if 'logfile' in vars(event): 1277*4882a593Smuzhiyun task_information['logfile'] = event.logfile 1278*4882a593Smuzhiyun 1279*4882a593Smuzhiyun if '_message' in vars(event): 1280*4882a593Smuzhiyun task_information['message'] = event._message 1281*4882a593Smuzhiyun 1282*4882a593Smuzhiyun if 'taskflags' in vars(event): 1283*4882a593Smuzhiyun # with TaskStarted, we get even more information 1284*4882a593Smuzhiyun if 'python' in event.taskflags.keys() and event.taskflags['python'] == '1': 1285*4882a593Smuzhiyun task_information['script_type'] = Task.CODING_PYTHON 1286*4882a593Smuzhiyun else: 1287*4882a593Smuzhiyun task_information['script_type'] = Task.CODING_SHELL 1288*4882a593Smuzhiyun 1289*4882a593Smuzhiyun if task_information['outcome'] == Task.OUTCOME_NA: 1290*4882a593Smuzhiyun if isinstance(event, (bb.runqueue.runQueueTaskCompleted, bb.runqueue.sceneQueueTaskCompleted)): 1291*4882a593Smuzhiyun task_information['outcome'] = Task.OUTCOME_SUCCESS 1292*4882a593Smuzhiyun del self.internal_state['taskdata'][identifier] 1293*4882a593Smuzhiyun 1294*4882a593Smuzhiyun if isinstance(event, (bb.runqueue.runQueueTaskFailed, bb.runqueue.sceneQueueTaskFailed)): 1295*4882a593Smuzhiyun task_information['outcome'] = Task.OUTCOME_FAILED 1296*4882a593Smuzhiyun del self.internal_state['taskdata'][identifier] 1297*4882a593Smuzhiyun 1298*4882a593Smuzhiyun # we force a sync point here, to get the progress bar to show 1299*4882a593Smuzhiyun if self.autocommit_step % 3 == 0: 1300*4882a593Smuzhiyun transaction.set_autocommit(True) 1301*4882a593Smuzhiyun transaction.set_autocommit(False) 1302*4882a593Smuzhiyun self.autocommit_step += 1 1303*4882a593Smuzhiyun 1304*4882a593Smuzhiyun self.orm_wrapper.get_update_task_object(task_information, True) # must exist 1305*4882a593Smuzhiyun 1306*4882a593Smuzhiyun 1307*4882a593Smuzhiyun def store_missed_state_tasks(self, event): 1308*4882a593Smuzhiyun for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['missed']: 1309*4882a593Smuzhiyun 1310*4882a593Smuzhiyun # identifier = fn + taskname + "_setscene" 1311*4882a593Smuzhiyun recipe_information = self._get_recipe_information_from_taskfile(fn) 1312*4882a593Smuzhiyun recipe = self.orm_wrapper.get_update_recipe_object(recipe_information) 1313*4882a593Smuzhiyun mevent = MockEvent() 1314*4882a593Smuzhiyun mevent.taskname = taskname 1315*4882a593Smuzhiyun mevent.taskhash = taskhash 1316*4882a593Smuzhiyun task_information = self._get_task_information(mevent,recipe) 1317*4882a593Smuzhiyun 1318*4882a593Smuzhiyun task_information['start_time'] = timezone.now() 1319*4882a593Smuzhiyun task_information['outcome'] = Task.OUTCOME_NA 1320*4882a593Smuzhiyun task_information['sstate_checksum'] = taskhash 1321*4882a593Smuzhiyun task_information['sstate_result'] = Task.SSTATE_MISS 1322*4882a593Smuzhiyun task_information['path_to_sstate_obj'] = sstatefile 1323*4882a593Smuzhiyun 1324*4882a593Smuzhiyun self.orm_wrapper.get_update_task_object(task_information) 1325*4882a593Smuzhiyun 1326*4882a593Smuzhiyun for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['found']: 1327*4882a593Smuzhiyun 1328*4882a593Smuzhiyun # identifier = fn + taskname + "_setscene" 1329*4882a593Smuzhiyun recipe_information = self._get_recipe_information_from_taskfile(fn) 1330*4882a593Smuzhiyun recipe = self.orm_wrapper.get_update_recipe_object(recipe_information) 1331*4882a593Smuzhiyun mevent = MockEvent() 1332*4882a593Smuzhiyun mevent.taskname = taskname 1333*4882a593Smuzhiyun mevent.taskhash = taskhash 1334*4882a593Smuzhiyun task_information = self._get_task_information(mevent,recipe) 1335*4882a593Smuzhiyun 1336*4882a593Smuzhiyun task_information['path_to_sstate_obj'] = sstatefile 1337*4882a593Smuzhiyun 1338*4882a593Smuzhiyun self.orm_wrapper.get_update_task_object(task_information) 1339*4882a593Smuzhiyun 1340*4882a593Smuzhiyun 1341*4882a593Smuzhiyun def store_target_package_data(self, event): 1342*4882a593Smuzhiyun self._ensure_build() 1343*4882a593Smuzhiyun 1344*4882a593Smuzhiyun # for all image targets 1345*4882a593Smuzhiyun for target in self.internal_state['targets']: 1346*4882a593Smuzhiyun if target.is_image: 1347*4882a593Smuzhiyun pkgdata = BuildInfoHelper._get_data_from_event(event)['pkgdata'] 1348*4882a593Smuzhiyun imgdata = BuildInfoHelper._get_data_from_event(event)['imgdata'].get(target.target, {}) 1349*4882a593Smuzhiyun filedata = BuildInfoHelper._get_data_from_event(event)['filedata'].get(target.target, {}) 1350*4882a593Smuzhiyun 1351*4882a593Smuzhiyun try: 1352*4882a593Smuzhiyun self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata, pkgdata, self.internal_state['recipes'], built_package=True) 1353*4882a593Smuzhiyun self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata.copy(), pkgdata, self.internal_state['recipes'], built_package=False) 1354*4882a593Smuzhiyun except KeyError as e: 1355*4882a593Smuzhiyun logger.warning("KeyError in save_target_package_information" 1356*4882a593Smuzhiyun "%s ", e) 1357*4882a593Smuzhiyun 1358*4882a593Smuzhiyun # only try to find files in the image if the task for this 1359*4882a593Smuzhiyun # target is one which produces image files; otherwise, the old 1360*4882a593Smuzhiyun # list of files in the files-in-image.txt file will be 1361*4882a593Smuzhiyun # appended to the target even if it didn't produce any images 1362*4882a593Smuzhiyun if target.task in BuildInfoHelper.IMAGE_GENERATING_TASKS: 1363*4882a593Smuzhiyun try: 1364*4882a593Smuzhiyun self.orm_wrapper.save_target_file_information(self.internal_state['build'], target, filedata) 1365*4882a593Smuzhiyun except KeyError as e: 1366*4882a593Smuzhiyun logger.warning("KeyError in save_target_file_information" 1367*4882a593Smuzhiyun "%s ", e) 1368*4882a593Smuzhiyun 1369*4882a593Smuzhiyun 1370*4882a593Smuzhiyun 1371*4882a593Smuzhiyun def cancel_cli_build(self): 1372*4882a593Smuzhiyun """ 1373*4882a593Smuzhiyun If a build is currently underway, set its state to CANCELLED; 1374*4882a593Smuzhiyun note that this only gets called for command line builds which are 1375*4882a593Smuzhiyun interrupted, so it doesn't touch any BuildRequest objects 1376*4882a593Smuzhiyun """ 1377*4882a593Smuzhiyun self._ensure_build() 1378*4882a593Smuzhiyun self.internal_state['build'].outcome = Build.CANCELLED 1379*4882a593Smuzhiyun self.internal_state['build'].save() 1380*4882a593Smuzhiyun signal_runbuilds() 1381*4882a593Smuzhiyun 1382*4882a593Smuzhiyun def store_dependency_information(self, event): 1383*4882a593Smuzhiyun assert '_depgraph' in vars(event) 1384*4882a593Smuzhiyun assert 'layer-priorities' in event._depgraph 1385*4882a593Smuzhiyun assert 'pn' in event._depgraph 1386*4882a593Smuzhiyun assert 'tdepends' in event._depgraph 1387*4882a593Smuzhiyun 1388*4882a593Smuzhiyun errormsg = [] 1389*4882a593Smuzhiyun 1390*4882a593Smuzhiyun # save layer version priorities 1391*4882a593Smuzhiyun if 'layer-priorities' in event._depgraph.keys(): 1392*4882a593Smuzhiyun for lv in event._depgraph['layer-priorities']: 1393*4882a593Smuzhiyun (_, path, _, priority) = lv 1394*4882a593Smuzhiyun layer_version_obj = self._get_layer_version_for_dependency(path) 1395*4882a593Smuzhiyun if layer_version_obj: 1396*4882a593Smuzhiyun layer_version_obj.priority = priority 1397*4882a593Smuzhiyun layer_version_obj.save() 1398*4882a593Smuzhiyun 1399*4882a593Smuzhiyun # save recipe information 1400*4882a593Smuzhiyun self.internal_state['recipes'] = {} 1401*4882a593Smuzhiyun for pn in event._depgraph['pn']: 1402*4882a593Smuzhiyun 1403*4882a593Smuzhiyun file_name = event._depgraph['pn'][pn]['filename'].split(":")[-1] 1404*4882a593Smuzhiyun pathflags = ":".join(sorted(event._depgraph['pn'][pn]['filename'].split(":")[:-1])) 1405*4882a593Smuzhiyun layer_version_obj = self._get_layer_version_for_path(file_name) 1406*4882a593Smuzhiyun 1407*4882a593Smuzhiyun assert layer_version_obj is not None 1408*4882a593Smuzhiyun 1409*4882a593Smuzhiyun recipe_info = {} 1410*4882a593Smuzhiyun recipe_info['name'] = pn 1411*4882a593Smuzhiyun recipe_info['layer_version'] = layer_version_obj 1412*4882a593Smuzhiyun 1413*4882a593Smuzhiyun if 'version' in event._depgraph['pn'][pn]: 1414*4882a593Smuzhiyun recipe_info['version'] = event._depgraph['pn'][pn]['version'].lstrip(":") 1415*4882a593Smuzhiyun 1416*4882a593Smuzhiyun if 'summary' in event._depgraph['pn'][pn]: 1417*4882a593Smuzhiyun recipe_info['summary'] = event._depgraph['pn'][pn]['summary'] 1418*4882a593Smuzhiyun 1419*4882a593Smuzhiyun if 'license' in event._depgraph['pn'][pn]: 1420*4882a593Smuzhiyun recipe_info['license'] = event._depgraph['pn'][pn]['license'] 1421*4882a593Smuzhiyun 1422*4882a593Smuzhiyun if 'description' in event._depgraph['pn'][pn]: 1423*4882a593Smuzhiyun recipe_info['description'] = event._depgraph['pn'][pn]['description'] 1424*4882a593Smuzhiyun 1425*4882a593Smuzhiyun if 'section' in event._depgraph['pn'][pn]: 1426*4882a593Smuzhiyun recipe_info['section'] = event._depgraph['pn'][pn]['section'] 1427*4882a593Smuzhiyun 1428*4882a593Smuzhiyun if 'homepage' in event._depgraph['pn'][pn]: 1429*4882a593Smuzhiyun recipe_info['homepage'] = event._depgraph['pn'][pn]['homepage'] 1430*4882a593Smuzhiyun 1431*4882a593Smuzhiyun if 'bugtracker' in event._depgraph['pn'][pn]: 1432*4882a593Smuzhiyun recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker'] 1433*4882a593Smuzhiyun 1434*4882a593Smuzhiyun recipe_info['file_path'] = file_name 1435*4882a593Smuzhiyun recipe_info['pathflags'] = pathflags 1436*4882a593Smuzhiyun 1437*4882a593Smuzhiyun if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path): 1438*4882a593Smuzhiyun recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/") 1439*4882a593Smuzhiyun else: 1440*4882a593Smuzhiyun raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path)) 1441*4882a593Smuzhiyun 1442*4882a593Smuzhiyun recipe = self.orm_wrapper.get_update_recipe_object(recipe_info) 1443*4882a593Smuzhiyun recipe.is_image = False 1444*4882a593Smuzhiyun if 'inherits' in event._depgraph['pn'][pn].keys(): 1445*4882a593Smuzhiyun for cls in event._depgraph['pn'][pn]['inherits']: 1446*4882a593Smuzhiyun if cls.endswith('/image.bbclass'): 1447*4882a593Smuzhiyun recipe.is_image = True 1448*4882a593Smuzhiyun recipe_info['is_image'] = True 1449*4882a593Smuzhiyun # Save the is_image state to the relevant recipe objects 1450*4882a593Smuzhiyun self.orm_wrapper.get_update_recipe_object(recipe_info) 1451*4882a593Smuzhiyun break 1452*4882a593Smuzhiyun if recipe.is_image: 1453*4882a593Smuzhiyun for t in self.internal_state['targets']: 1454*4882a593Smuzhiyun if pn == t.target: 1455*4882a593Smuzhiyun t.is_image = True 1456*4882a593Smuzhiyun t.save() 1457*4882a593Smuzhiyun self.internal_state['recipes'][pn] = recipe 1458*4882a593Smuzhiyun 1459*4882a593Smuzhiyun # we'll not get recipes for key w/ values listed in ASSUME_PROVIDED 1460*4882a593Smuzhiyun 1461*4882a593Smuzhiyun assume_provided = self.server.runCommand(["getVariable", "ASSUME_PROVIDED"])[0].split() 1462*4882a593Smuzhiyun 1463*4882a593Smuzhiyun # save recipe dependency 1464*4882a593Smuzhiyun # buildtime 1465*4882a593Smuzhiyun recipedeps_objects = [] 1466*4882a593Smuzhiyun for recipe in event._depgraph['depends']: 1467*4882a593Smuzhiyun target = self.internal_state['recipes'][recipe] 1468*4882a593Smuzhiyun for dep in event._depgraph['depends'][recipe]: 1469*4882a593Smuzhiyun if dep in assume_provided: 1470*4882a593Smuzhiyun continue 1471*4882a593Smuzhiyun via = None 1472*4882a593Smuzhiyun if 'providermap' in event._depgraph and dep in event._depgraph['providermap']: 1473*4882a593Smuzhiyun deprecipe = event._depgraph['providermap'][dep][0] 1474*4882a593Smuzhiyun dependency = self.internal_state['recipes'][deprecipe] 1475*4882a593Smuzhiyun via = Provides.objects.get_or_create(name=dep, 1476*4882a593Smuzhiyun recipe=dependency)[0] 1477*4882a593Smuzhiyun elif dep in self.internal_state['recipes']: 1478*4882a593Smuzhiyun dependency = self.internal_state['recipes'][dep] 1479*4882a593Smuzhiyun else: 1480*4882a593Smuzhiyun errormsg.append(" stpd: KeyError saving recipe dependency for %s, %s \n" % (recipe, dep)) 1481*4882a593Smuzhiyun continue 1482*4882a593Smuzhiyun recipe_dep = Recipe_Dependency(recipe=target, 1483*4882a593Smuzhiyun depends_on=dependency, 1484*4882a593Smuzhiyun via=via, 1485*4882a593Smuzhiyun dep_type=Recipe_Dependency.TYPE_DEPENDS) 1486*4882a593Smuzhiyun recipedeps_objects.append(recipe_dep) 1487*4882a593Smuzhiyun 1488*4882a593Smuzhiyun Recipe_Dependency.objects.bulk_create(recipedeps_objects) 1489*4882a593Smuzhiyun 1490*4882a593Smuzhiyun # save all task information 1491*4882a593Smuzhiyun def _save_a_task(taskdesc): 1492*4882a593Smuzhiyun spec = re.split(r'\.', taskdesc) 1493*4882a593Smuzhiyun pn = ".".join(spec[0:-1]) 1494*4882a593Smuzhiyun taskname = spec[-1] 1495*4882a593Smuzhiyun e = event 1496*4882a593Smuzhiyun e.taskname = pn 1497*4882a593Smuzhiyun recipe = self.internal_state['recipes'][pn] 1498*4882a593Smuzhiyun task_info = self._get_task_information(e, recipe) 1499*4882a593Smuzhiyun task_info['task_name'] = taskname 1500*4882a593Smuzhiyun task_obj = self.orm_wrapper.get_update_task_object(task_info) 1501*4882a593Smuzhiyun return task_obj 1502*4882a593Smuzhiyun 1503*4882a593Smuzhiyun # create tasks 1504*4882a593Smuzhiyun tasks = {} 1505*4882a593Smuzhiyun for taskdesc in event._depgraph['tdepends']: 1506*4882a593Smuzhiyun tasks[taskdesc] = _save_a_task(taskdesc) 1507*4882a593Smuzhiyun 1508*4882a593Smuzhiyun # create dependencies between tasks 1509*4882a593Smuzhiyun taskdeps_objects = [] 1510*4882a593Smuzhiyun for taskdesc in event._depgraph['tdepends']: 1511*4882a593Smuzhiyun target = tasks[taskdesc] 1512*4882a593Smuzhiyun for taskdep in event._depgraph['tdepends'][taskdesc]: 1513*4882a593Smuzhiyun if taskdep not in tasks: 1514*4882a593Smuzhiyun # Fetch tasks info is not collected previously 1515*4882a593Smuzhiyun dep = _save_a_task(taskdep) 1516*4882a593Smuzhiyun else: 1517*4882a593Smuzhiyun dep = tasks[taskdep] 1518*4882a593Smuzhiyun taskdeps_objects.append(Task_Dependency( task = target, depends_on = dep )) 1519*4882a593Smuzhiyun Task_Dependency.objects.bulk_create(taskdeps_objects) 1520*4882a593Smuzhiyun 1521*4882a593Smuzhiyun if errormsg: 1522*4882a593Smuzhiyun logger.warning("buildinfohelper: dependency info not identify recipes: \n%s", "".join(errormsg)) 1523*4882a593Smuzhiyun 1524*4882a593Smuzhiyun 1525*4882a593Smuzhiyun def store_build_package_information(self, event): 1526*4882a593Smuzhiyun self._ensure_build() 1527*4882a593Smuzhiyun 1528*4882a593Smuzhiyun package_info = BuildInfoHelper._get_data_from_event(event) 1529*4882a593Smuzhiyun self.orm_wrapper.save_build_package_information( 1530*4882a593Smuzhiyun self.internal_state['build'], 1531*4882a593Smuzhiyun package_info, 1532*4882a593Smuzhiyun self.internal_state['recipes'], 1533*4882a593Smuzhiyun built_package=True) 1534*4882a593Smuzhiyun 1535*4882a593Smuzhiyun self.orm_wrapper.save_build_package_information( 1536*4882a593Smuzhiyun self.internal_state['build'], 1537*4882a593Smuzhiyun package_info, 1538*4882a593Smuzhiyun self.internal_state['recipes'], 1539*4882a593Smuzhiyun built_package=False) 1540*4882a593Smuzhiyun 1541*4882a593Smuzhiyun def _store_build_done(self, errorcode): 1542*4882a593Smuzhiyun logger.info("Build exited with errorcode %d", errorcode) 1543*4882a593Smuzhiyun 1544*4882a593Smuzhiyun if not self.brbe: 1545*4882a593Smuzhiyun return 1546*4882a593Smuzhiyun 1547*4882a593Smuzhiyun br_id, be_id = self.brbe.split(":") 1548*4882a593Smuzhiyun 1549*4882a593Smuzhiyun br = BuildRequest.objects.get(pk = br_id) 1550*4882a593Smuzhiyun 1551*4882a593Smuzhiyun # if we're 'done' because we got cancelled update the build outcome 1552*4882a593Smuzhiyun if br.state == BuildRequest.REQ_CANCELLING: 1553*4882a593Smuzhiyun logger.info("Build cancelled") 1554*4882a593Smuzhiyun br.build.outcome = Build.CANCELLED 1555*4882a593Smuzhiyun br.build.save() 1556*4882a593Smuzhiyun self.internal_state['build'] = br.build 1557*4882a593Smuzhiyun errorcode = 0 1558*4882a593Smuzhiyun 1559*4882a593Smuzhiyun if errorcode == 0: 1560*4882a593Smuzhiyun # request archival of the project artifacts 1561*4882a593Smuzhiyun br.state = BuildRequest.REQ_COMPLETED 1562*4882a593Smuzhiyun else: 1563*4882a593Smuzhiyun br.state = BuildRequest.REQ_FAILED 1564*4882a593Smuzhiyun br.save() 1565*4882a593Smuzhiyun 1566*4882a593Smuzhiyun be = BuildEnvironment.objects.get(pk = be_id) 1567*4882a593Smuzhiyun be.lock = BuildEnvironment.LOCK_FREE 1568*4882a593Smuzhiyun be.save() 1569*4882a593Smuzhiyun signal_runbuilds() 1570*4882a593Smuzhiyun 1571*4882a593Smuzhiyun def store_log_error(self, text): 1572*4882a593Smuzhiyun mockevent = MockEvent() 1573*4882a593Smuzhiyun mockevent.levelno = formatter.ERROR 1574*4882a593Smuzhiyun mockevent.msg = text 1575*4882a593Smuzhiyun mockevent.pathname = '-- None' 1576*4882a593Smuzhiyun mockevent.lineno = LogMessage.ERROR 1577*4882a593Smuzhiyun self.store_log_event(mockevent) 1578*4882a593Smuzhiyun 1579*4882a593Smuzhiyun def store_log_exception(self, text, backtrace = ""): 1580*4882a593Smuzhiyun mockevent = MockEvent() 1581*4882a593Smuzhiyun mockevent.levelno = -1 1582*4882a593Smuzhiyun mockevent.msg = text 1583*4882a593Smuzhiyun mockevent.pathname = backtrace 1584*4882a593Smuzhiyun mockevent.lineno = -1 1585*4882a593Smuzhiyun self.store_log_event(mockevent) 1586*4882a593Smuzhiyun 1587*4882a593Smuzhiyun def store_log_event(self, event,cli_backlog=True): 1588*4882a593Smuzhiyun self._ensure_build() 1589*4882a593Smuzhiyun 1590*4882a593Smuzhiyun if event.levelno < formatter.WARNING: 1591*4882a593Smuzhiyun return 1592*4882a593Smuzhiyun 1593*4882a593Smuzhiyun # early return for CLI builds 1594*4882a593Smuzhiyun if cli_backlog and self.brbe is None: 1595*4882a593Smuzhiyun if not 'backlog' in self.internal_state: 1596*4882a593Smuzhiyun self.internal_state['backlog'] = [] 1597*4882a593Smuzhiyun self.internal_state['backlog'].append(event) 1598*4882a593Smuzhiyun return 1599*4882a593Smuzhiyun 1600*4882a593Smuzhiyun if 'backlog' in self.internal_state: 1601*4882a593Smuzhiyun # if we have a backlog of events, do our best to save them here 1602*4882a593Smuzhiyun if self.internal_state['backlog']: 1603*4882a593Smuzhiyun tempevent = self.internal_state['backlog'].pop() 1604*4882a593Smuzhiyun logger.debug("buildinfohelper: Saving stored event %s " 1605*4882a593Smuzhiyun % tempevent) 1606*4882a593Smuzhiyun self.store_log_event(tempevent,cli_backlog) 1607*4882a593Smuzhiyun else: 1608*4882a593Smuzhiyun logger.info("buildinfohelper: All events saved") 1609*4882a593Smuzhiyun del self.internal_state['backlog'] 1610*4882a593Smuzhiyun 1611*4882a593Smuzhiyun log_information = {} 1612*4882a593Smuzhiyun log_information['build'] = self.internal_state['build'] 1613*4882a593Smuzhiyun if event.levelno == formatter.CRITICAL: 1614*4882a593Smuzhiyun log_information['level'] = LogMessage.CRITICAL 1615*4882a593Smuzhiyun elif event.levelno == formatter.ERROR: 1616*4882a593Smuzhiyun log_information['level'] = LogMessage.ERROR 1617*4882a593Smuzhiyun elif event.levelno == formatter.WARNING: 1618*4882a593Smuzhiyun log_information['level'] = LogMessage.WARNING 1619*4882a593Smuzhiyun elif event.levelno == -2: # toaster self-logging 1620*4882a593Smuzhiyun log_information['level'] = -2 1621*4882a593Smuzhiyun else: 1622*4882a593Smuzhiyun log_information['level'] = LogMessage.INFO 1623*4882a593Smuzhiyun 1624*4882a593Smuzhiyun log_information['message'] = event.getMessage() 1625*4882a593Smuzhiyun log_information['pathname'] = event.pathname 1626*4882a593Smuzhiyun log_information['lineno'] = event.lineno 1627*4882a593Smuzhiyun logger.info("Logging error 2: %s", log_information) 1628*4882a593Smuzhiyun 1629*4882a593Smuzhiyun self.orm_wrapper.create_logmessage(log_information) 1630*4882a593Smuzhiyun 1631*4882a593Smuzhiyun def _get_filenames_from_image_license(self, image_license_manifest_path): 1632*4882a593Smuzhiyun """ 1633*4882a593Smuzhiyun Find the FILES line in the image_license.manifest file, 1634*4882a593Smuzhiyun which has the basenames of the bzImage and modules files 1635*4882a593Smuzhiyun in this format: 1636*4882a593Smuzhiyun FILES: bzImage--4.4.11+git0+3a5f494784_53e84104c5-r0-qemux86-20160603165040.bin modules--4.4.11+git0+3a5f494784_53e84104c5-r0-qemux86-20160603165040.tgz 1637*4882a593Smuzhiyun """ 1638*4882a593Smuzhiyun files = [] 1639*4882a593Smuzhiyun with open(image_license_manifest_path) as image_license: 1640*4882a593Smuzhiyun for line in image_license: 1641*4882a593Smuzhiyun if line.startswith('FILES'): 1642*4882a593Smuzhiyun files_str = line.split(':')[1].strip() 1643*4882a593Smuzhiyun files_str = re.sub(r' {2,}', ' ', files_str) 1644*4882a593Smuzhiyun 1645*4882a593Smuzhiyun # ignore lines like "FILES:" with no filenames 1646*4882a593Smuzhiyun if files_str: 1647*4882a593Smuzhiyun files += files_str.split(' ') 1648*4882a593Smuzhiyun return files 1649*4882a593Smuzhiyun 1650*4882a593Smuzhiyun def _endswith(self, str_to_test, endings): 1651*4882a593Smuzhiyun """ 1652*4882a593Smuzhiyun Returns True if str ends with one of the strings in the list 1653*4882a593Smuzhiyun endings, False otherwise 1654*4882a593Smuzhiyun """ 1655*4882a593Smuzhiyun endswith = False 1656*4882a593Smuzhiyun for ending in endings: 1657*4882a593Smuzhiyun if str_to_test.endswith(ending): 1658*4882a593Smuzhiyun endswith = True 1659*4882a593Smuzhiyun break 1660*4882a593Smuzhiyun return endswith 1661*4882a593Smuzhiyun 1662*4882a593Smuzhiyun def scan_task_artifacts(self, event): 1663*4882a593Smuzhiyun """ 1664*4882a593Smuzhiyun The 'TaskArtifacts' event passes the manifest file content for the 1665*4882a593Smuzhiyun tasks 'do_deploy', 'do_image_complete', 'do_populate_sdk', and 1666*4882a593Smuzhiyun 'do_populate_sdk_ext'. The first two will be implemented later. 1667*4882a593Smuzhiyun """ 1668*4882a593Smuzhiyun task_vars = BuildInfoHelper._get_data_from_event(event) 1669*4882a593Smuzhiyun task_name = task_vars['task'][task_vars['task'].find(':')+1:] 1670*4882a593Smuzhiyun task_artifacts = task_vars['artifacts'] 1671*4882a593Smuzhiyun 1672*4882a593Smuzhiyun if task_name in ['do_populate_sdk', 'do_populate_sdk_ext']: 1673*4882a593Smuzhiyun targets = [target for target in self.internal_state['targets'] \ 1674*4882a593Smuzhiyun if target.task == task_name[3:]] 1675*4882a593Smuzhiyun if not targets: 1676*4882a593Smuzhiyun logger.warning("scan_task_artifacts: SDK targets not found: %s\n", task_name) 1677*4882a593Smuzhiyun return 1678*4882a593Smuzhiyun for artifact_path in task_artifacts: 1679*4882a593Smuzhiyun if not os.path.isfile(artifact_path): 1680*4882a593Smuzhiyun logger.warning("scan_task_artifacts: artifact file not found: %s\n", artifact_path) 1681*4882a593Smuzhiyun continue 1682*4882a593Smuzhiyun for target in targets: 1683*4882a593Smuzhiyun # don't record the file if it's already been added 1684*4882a593Smuzhiyun # to this target 1685*4882a593Smuzhiyun matching_files = TargetSDKFile.objects.filter( 1686*4882a593Smuzhiyun target=target, file_name=artifact_path) 1687*4882a593Smuzhiyun if matching_files.count() == 0: 1688*4882a593Smuzhiyun artifact_size = os.stat(artifact_path).st_size 1689*4882a593Smuzhiyun self.orm_wrapper.save_target_sdk_file( 1690*4882a593Smuzhiyun target, artifact_path, artifact_size) 1691*4882a593Smuzhiyun 1692*4882a593Smuzhiyun def _get_image_files(self, deploy_dir_image, image_name, image_file_extensions): 1693*4882a593Smuzhiyun """ 1694*4882a593Smuzhiyun Find files in deploy_dir_image whose basename starts with the 1695*4882a593Smuzhiyun string image_name and ends with one of the strings in 1696*4882a593Smuzhiyun image_file_extensions. 1697*4882a593Smuzhiyun 1698*4882a593Smuzhiyun Returns a list of file dictionaries like 1699*4882a593Smuzhiyun 1700*4882a593Smuzhiyun [ 1701*4882a593Smuzhiyun { 1702*4882a593Smuzhiyun 'path': '/path/to/image/file', 1703*4882a593Smuzhiyun 'size': <file size in bytes> 1704*4882a593Smuzhiyun } 1705*4882a593Smuzhiyun ] 1706*4882a593Smuzhiyun """ 1707*4882a593Smuzhiyun image_files = [] 1708*4882a593Smuzhiyun 1709*4882a593Smuzhiyun for dirpath, _, filenames in os.walk(deploy_dir_image): 1710*4882a593Smuzhiyun for filename in filenames: 1711*4882a593Smuzhiyun if filename.startswith(image_name) and \ 1712*4882a593Smuzhiyun self._endswith(filename, image_file_extensions): 1713*4882a593Smuzhiyun image_file_path = os.path.join(dirpath, filename) 1714*4882a593Smuzhiyun image_file_size = os.stat(image_file_path).st_size 1715*4882a593Smuzhiyun 1716*4882a593Smuzhiyun image_files.append({ 1717*4882a593Smuzhiyun 'path': image_file_path, 1718*4882a593Smuzhiyun 'size': image_file_size 1719*4882a593Smuzhiyun }) 1720*4882a593Smuzhiyun 1721*4882a593Smuzhiyun return image_files 1722*4882a593Smuzhiyun 1723*4882a593Smuzhiyun def scan_image_artifacts(self): 1724*4882a593Smuzhiyun """ 1725*4882a593Smuzhiyun Scan for built image artifacts in DEPLOY_DIR_IMAGE and associate them 1726*4882a593Smuzhiyun with a Target object in self.internal_state['targets']. 1727*4882a593Smuzhiyun 1728*4882a593Smuzhiyun We have two situations to handle: 1729*4882a593Smuzhiyun 1730*4882a593Smuzhiyun 1. This is the first time a target + machine has been built, so 1731*4882a593Smuzhiyun add files from the DEPLOY_DIR_IMAGE to the target. 1732*4882a593Smuzhiyun 1733*4882a593Smuzhiyun OR 1734*4882a593Smuzhiyun 1735*4882a593Smuzhiyun 2. There are no new files for the target (they were already produced by 1736*4882a593Smuzhiyun a previous build), so copy them from the most recent previous build with 1737*4882a593Smuzhiyun the same target, task and machine. 1738*4882a593Smuzhiyun """ 1739*4882a593Smuzhiyun deploy_dir_image = \ 1740*4882a593Smuzhiyun self.server.runCommand(['getVariable', 'DEPLOY_DIR_IMAGE'])[0] 1741*4882a593Smuzhiyun 1742*4882a593Smuzhiyun # if there's no DEPLOY_DIR_IMAGE, there aren't going to be 1743*4882a593Smuzhiyun # any image artifacts, so we can return immediately 1744*4882a593Smuzhiyun if not deploy_dir_image: 1745*4882a593Smuzhiyun return 1746*4882a593Smuzhiyun 1747*4882a593Smuzhiyun buildname = self.server.runCommand(['getVariable', 'BUILDNAME'])[0] 1748*4882a593Smuzhiyun machine = self.server.runCommand(['getVariable', 'MACHINE'])[0] 1749*4882a593Smuzhiyun image_name = self.server.runCommand(['getVariable', 'IMAGE_NAME'])[0] 1750*4882a593Smuzhiyun 1751*4882a593Smuzhiyun # location of the manifest files for this build; 1752*4882a593Smuzhiyun # note that this file is only produced if an image is produced 1753*4882a593Smuzhiyun license_directory = \ 1754*4882a593Smuzhiyun self.server.runCommand(['getVariable', 'LICENSE_DIRECTORY'])[0] 1755*4882a593Smuzhiyun 1756*4882a593Smuzhiyun # file name extensions for image files 1757*4882a593Smuzhiyun image_file_extensions_unique = {} 1758*4882a593Smuzhiyun image_fstypes = self.server.runCommand( 1759*4882a593Smuzhiyun ['getVariable', 'IMAGE_FSTYPES'])[0] 1760*4882a593Smuzhiyun if image_fstypes is not None: 1761*4882a593Smuzhiyun image_types_str = image_fstypes.strip() 1762*4882a593Smuzhiyun image_file_extensions = re.sub(r' {2,}', ' ', image_types_str) 1763*4882a593Smuzhiyun image_file_extensions_unique = set(image_file_extensions.split(' ')) 1764*4882a593Smuzhiyun 1765*4882a593Smuzhiyun targets = self.internal_state['targets'] 1766*4882a593Smuzhiyun 1767*4882a593Smuzhiyun # filter out anything which isn't an image target 1768*4882a593Smuzhiyun image_targets = [target for target in targets if target.is_image] 1769*4882a593Smuzhiyun 1770*4882a593Smuzhiyun for image_target in image_targets: 1771*4882a593Smuzhiyun # this is set to True if we find at least one file relating to 1772*4882a593Smuzhiyun # this target; if this remains False after the scan, we copy the 1773*4882a593Smuzhiyun # files from the most-recent Target with the same target + machine 1774*4882a593Smuzhiyun # onto this Target instead 1775*4882a593Smuzhiyun has_files = False 1776*4882a593Smuzhiyun 1777*4882a593Smuzhiyun # we construct this because by the time we reach 1778*4882a593Smuzhiyun # BuildCompleted, this has reset to 1779*4882a593Smuzhiyun # 'defaultpkgname-<MACHINE>-<BUILDNAME>'; 1780*4882a593Smuzhiyun # we need to change it to 1781*4882a593Smuzhiyun # <TARGET>-<MACHINE>-<BUILDNAME> 1782*4882a593Smuzhiyun real_image_name = re.sub(r'^defaultpkgname', image_target.target, 1783*4882a593Smuzhiyun image_name) 1784*4882a593Smuzhiyun 1785*4882a593Smuzhiyun image_license_manifest_path = os.path.join( 1786*4882a593Smuzhiyun license_directory, 1787*4882a593Smuzhiyun real_image_name, 1788*4882a593Smuzhiyun 'image_license.manifest') 1789*4882a593Smuzhiyun 1790*4882a593Smuzhiyun image_package_manifest_path = os.path.join( 1791*4882a593Smuzhiyun license_directory, 1792*4882a593Smuzhiyun real_image_name, 1793*4882a593Smuzhiyun 'image_license.manifest') 1794*4882a593Smuzhiyun 1795*4882a593Smuzhiyun # if image_license.manifest exists, we can read the names of 1796*4882a593Smuzhiyun # bzImage, modules etc. files for this build from it, then look for 1797*4882a593Smuzhiyun # them in the DEPLOY_DIR_IMAGE; note that this file is only produced 1798*4882a593Smuzhiyun # if an image file was produced 1799*4882a593Smuzhiyun if os.path.isfile(image_license_manifest_path): 1800*4882a593Smuzhiyun has_files = True 1801*4882a593Smuzhiyun 1802*4882a593Smuzhiyun basenames = self._get_filenames_from_image_license( 1803*4882a593Smuzhiyun image_license_manifest_path) 1804*4882a593Smuzhiyun 1805*4882a593Smuzhiyun for basename in basenames: 1806*4882a593Smuzhiyun artifact_path = os.path.join(deploy_dir_image, basename) 1807*4882a593Smuzhiyun if not os.path.exists(artifact_path): 1808*4882a593Smuzhiyun logger.warning("artifact %s doesn't exist, skipping" % artifact_path) 1809*4882a593Smuzhiyun continue 1810*4882a593Smuzhiyun artifact_size = os.stat(artifact_path).st_size 1811*4882a593Smuzhiyun 1812*4882a593Smuzhiyun # note that the artifact will only be saved against this 1813*4882a593Smuzhiyun # build if it hasn't been already 1814*4882a593Smuzhiyun self.orm_wrapper.save_target_kernel_file(image_target, 1815*4882a593Smuzhiyun artifact_path, artifact_size) 1816*4882a593Smuzhiyun 1817*4882a593Smuzhiyun # store the license manifest path on the target 1818*4882a593Smuzhiyun # (this file is also created any time an image file is created) 1819*4882a593Smuzhiyun license_manifest_path = os.path.join(license_directory, 1820*4882a593Smuzhiyun real_image_name, 'license.manifest') 1821*4882a593Smuzhiyun 1822*4882a593Smuzhiyun self.orm_wrapper.update_target_set_license_manifest( 1823*4882a593Smuzhiyun image_target, license_manifest_path) 1824*4882a593Smuzhiyun 1825*4882a593Smuzhiyun # store the package manifest path on the target (this file 1826*4882a593Smuzhiyun # is created any time an image file is created) 1827*4882a593Smuzhiyun package_manifest_path = os.path.join(deploy_dir_image, 1828*4882a593Smuzhiyun real_image_name + '.rootfs.manifest') 1829*4882a593Smuzhiyun 1830*4882a593Smuzhiyun if os.path.exists(package_manifest_path): 1831*4882a593Smuzhiyun self.orm_wrapper.update_target_set_package_manifest( 1832*4882a593Smuzhiyun image_target, package_manifest_path) 1833*4882a593Smuzhiyun 1834*4882a593Smuzhiyun # scan the directory for image files relating to this build 1835*4882a593Smuzhiyun # (via real_image_name); note that we don't have to set 1836*4882a593Smuzhiyun # has_files = True, as searching for the license manifest file 1837*4882a593Smuzhiyun # will already have set it to true if at least one image file was 1838*4882a593Smuzhiyun # produced; note that the real_image_name includes BUILDNAME, which 1839*4882a593Smuzhiyun # in turn includes a timestamp; so if no files were produced for 1840*4882a593Smuzhiyun # this timestamp (i.e. the build reused existing image files already 1841*4882a593Smuzhiyun # in the directory), no files will be recorded against this target 1842*4882a593Smuzhiyun image_files = self._get_image_files(deploy_dir_image, 1843*4882a593Smuzhiyun real_image_name, image_file_extensions_unique) 1844*4882a593Smuzhiyun 1845*4882a593Smuzhiyun for image_file in image_files: 1846*4882a593Smuzhiyun self.orm_wrapper.save_target_image_file_information( 1847*4882a593Smuzhiyun image_target, image_file['path'], image_file['size']) 1848*4882a593Smuzhiyun 1849*4882a593Smuzhiyun if not has_files: 1850*4882a593Smuzhiyun # copy image files and build artifacts from the 1851*4882a593Smuzhiyun # most-recently-built Target with the 1852*4882a593Smuzhiyun # same target + machine as this Target; also copy the license 1853*4882a593Smuzhiyun # manifest path, as that is not treated as an artifact and needs 1854*4882a593Smuzhiyun # to be set separately 1855*4882a593Smuzhiyun similar_target = \ 1856*4882a593Smuzhiyun self.orm_wrapper.get_similar_target_with_image_files( 1857*4882a593Smuzhiyun image_target) 1858*4882a593Smuzhiyun 1859*4882a593Smuzhiyun if similar_target: 1860*4882a593Smuzhiyun logger.info('image artifacts for target %s cloned from ' \ 1861*4882a593Smuzhiyun 'target %s' % (image_target.pk, similar_target.pk)) 1862*4882a593Smuzhiyun self.orm_wrapper.clone_image_artifacts(similar_target, 1863*4882a593Smuzhiyun image_target) 1864*4882a593Smuzhiyun 1865*4882a593Smuzhiyun def _get_sdk_targets(self): 1866*4882a593Smuzhiyun """ 1867*4882a593Smuzhiyun Return targets which could generate SDK artifacts, i.e. 1868*4882a593Smuzhiyun "do_populate_sdk" and "do_populate_sdk_ext". 1869*4882a593Smuzhiyun """ 1870*4882a593Smuzhiyun return [target for target in self.internal_state['targets'] \ 1871*4882a593Smuzhiyun if target.task in ['populate_sdk', 'populate_sdk_ext']] 1872*4882a593Smuzhiyun 1873*4882a593Smuzhiyun def scan_sdk_artifacts(self, event): 1874*4882a593Smuzhiyun """ 1875*4882a593Smuzhiyun Note that we have to intercept an SDKArtifactInfo event from 1876*4882a593Smuzhiyun toaster.bbclass (via toasterui) to get hold of the SDK variables we 1877*4882a593Smuzhiyun need to be able to scan for files accurately: this is because 1878*4882a593Smuzhiyun variables like TOOLCHAIN_OUTPUTNAME have reset to None by the time 1879*4882a593Smuzhiyun BuildCompleted is fired by bitbake, so we have to get those values 1880*4882a593Smuzhiyun while the build is still in progress. 1881*4882a593Smuzhiyun 1882*4882a593Smuzhiyun For populate_sdk_ext, this runs twice, with two different 1883*4882a593Smuzhiyun TOOLCHAIN_OUTPUTNAME settings, each of which will capture some of the 1884*4882a593Smuzhiyun files in the SDK output directory. 1885*4882a593Smuzhiyun """ 1886*4882a593Smuzhiyun sdk_vars = BuildInfoHelper._get_data_from_event(event) 1887*4882a593Smuzhiyun toolchain_outputname = sdk_vars['TOOLCHAIN_OUTPUTNAME'] 1888*4882a593Smuzhiyun 1889*4882a593Smuzhiyun # targets which might have created SDK artifacts 1890*4882a593Smuzhiyun sdk_targets = self._get_sdk_targets() 1891*4882a593Smuzhiyun 1892*4882a593Smuzhiyun # location of SDK artifacts 1893*4882a593Smuzhiyun tmpdir = self.server.runCommand(['getVariable', 'TMPDIR'])[0] 1894*4882a593Smuzhiyun sdk_dir = os.path.join(tmpdir, 'deploy', 'sdk') 1895*4882a593Smuzhiyun 1896*4882a593Smuzhiyun # all files in the SDK directory 1897*4882a593Smuzhiyun artifacts = [] 1898*4882a593Smuzhiyun for dir_path, _, filenames in os.walk(sdk_dir): 1899*4882a593Smuzhiyun for filename in filenames: 1900*4882a593Smuzhiyun full_path = os.path.join(dir_path, filename) 1901*4882a593Smuzhiyun if not os.path.islink(full_path): 1902*4882a593Smuzhiyun artifacts.append(full_path) 1903*4882a593Smuzhiyun 1904*4882a593Smuzhiyun for sdk_target in sdk_targets: 1905*4882a593Smuzhiyun # find files in the SDK directory which haven't already been 1906*4882a593Smuzhiyun # recorded against a Target and whose basename matches 1907*4882a593Smuzhiyun # TOOLCHAIN_OUTPUTNAME 1908*4882a593Smuzhiyun for artifact_path in artifacts: 1909*4882a593Smuzhiyun basename = os.path.basename(artifact_path) 1910*4882a593Smuzhiyun 1911*4882a593Smuzhiyun toolchain_match = basename.startswith(toolchain_outputname) 1912*4882a593Smuzhiyun 1913*4882a593Smuzhiyun # files which match the name of the target which produced them; 1914*4882a593Smuzhiyun # for example, 1915*4882a593Smuzhiyun # poky-glibc-x86_64-core-image-sato-i586-toolchain-ext-2.1+snapshot.sh 1916*4882a593Smuzhiyun target_match = re.search(sdk_target.target, basename) 1917*4882a593Smuzhiyun 1918*4882a593Smuzhiyun # targets which produce "*-nativesdk-*" files 1919*4882a593Smuzhiyun is_ext_sdk_target = sdk_target.task in \ 1920*4882a593Smuzhiyun ['do_populate_sdk_ext', 'populate_sdk_ext'] 1921*4882a593Smuzhiyun 1922*4882a593Smuzhiyun # SDK files which don't match the target name, i.e. 1923*4882a593Smuzhiyun # x86_64-nativesdk-libc.* 1924*4882a593Smuzhiyun # poky-glibc-x86_64-buildtools-tarball-i586-buildtools-nativesdk-standalone-2.1+snapshot* 1925*4882a593Smuzhiyun is_ext_sdk_file = re.search('-nativesdk-', basename) 1926*4882a593Smuzhiyun 1927*4882a593Smuzhiyun file_from_target = (toolchain_match and target_match) or \ 1928*4882a593Smuzhiyun (is_ext_sdk_target and is_ext_sdk_file) 1929*4882a593Smuzhiyun 1930*4882a593Smuzhiyun if file_from_target: 1931*4882a593Smuzhiyun # don't record the file if it's already been added to this 1932*4882a593Smuzhiyun # target 1933*4882a593Smuzhiyun matching_files = TargetSDKFile.objects.filter( 1934*4882a593Smuzhiyun target=sdk_target, file_name=artifact_path) 1935*4882a593Smuzhiyun 1936*4882a593Smuzhiyun if matching_files.count() == 0: 1937*4882a593Smuzhiyun artifact_size = os.stat(artifact_path).st_size 1938*4882a593Smuzhiyun 1939*4882a593Smuzhiyun self.orm_wrapper.save_target_sdk_file( 1940*4882a593Smuzhiyun sdk_target, artifact_path, artifact_size) 1941*4882a593Smuzhiyun 1942*4882a593Smuzhiyun def clone_required_sdk_artifacts(self): 1943*4882a593Smuzhiyun """ 1944*4882a593Smuzhiyun If an SDK target doesn't have any SDK artifacts, this means that 1945*4882a593Smuzhiyun the postfuncs of populate_sdk or populate_sdk_ext didn't fire, which 1946*4882a593Smuzhiyun in turn means that the targets of this build didn't generate any new 1947*4882a593Smuzhiyun artifacts. 1948*4882a593Smuzhiyun 1949*4882a593Smuzhiyun In this case, clone SDK artifacts for targets in the current build 1950*4882a593Smuzhiyun from existing targets for this build. 1951*4882a593Smuzhiyun """ 1952*4882a593Smuzhiyun sdk_targets = self._get_sdk_targets() 1953*4882a593Smuzhiyun for sdk_target in sdk_targets: 1954*4882a593Smuzhiyun # only clone for SDK targets which have no TargetSDKFiles yet 1955*4882a593Smuzhiyun if sdk_target.targetsdkfile_set.all().count() == 0: 1956*4882a593Smuzhiyun similar_target = \ 1957*4882a593Smuzhiyun self.orm_wrapper.get_similar_target_with_sdk_files( 1958*4882a593Smuzhiyun sdk_target) 1959*4882a593Smuzhiyun if similar_target: 1960*4882a593Smuzhiyun logger.info('SDK artifacts for target %s cloned from ' \ 1961*4882a593Smuzhiyun 'target %s' % (sdk_target.pk, similar_target.pk)) 1962*4882a593Smuzhiyun self.orm_wrapper.clone_sdk_artifacts(similar_target, 1963*4882a593Smuzhiyun sdk_target) 1964*4882a593Smuzhiyun 1965*4882a593Smuzhiyun def close(self, errorcode): 1966*4882a593Smuzhiyun self._store_build_done(errorcode) 1967*4882a593Smuzhiyun 1968*4882a593Smuzhiyun if 'backlog' in self.internal_state: 1969*4882a593Smuzhiyun # we save missed events in the database for the current build 1970*4882a593Smuzhiyun tempevent = self.internal_state['backlog'].pop() 1971*4882a593Smuzhiyun # Do not skip command line build events 1972*4882a593Smuzhiyun self.store_log_event(tempevent,False) 1973*4882a593Smuzhiyun 1974*4882a593Smuzhiyun 1975*4882a593Smuzhiyun # unset the brbe; this is to prevent subsequent command-line builds 1976*4882a593Smuzhiyun # being incorrectly attached to the previous Toaster-triggered build; 1977*4882a593Smuzhiyun # see https://bugzilla.yoctoproject.org/show_bug.cgi?id=9021 1978*4882a593Smuzhiyun self.brbe = None 1979*4882a593Smuzhiyun 1980*4882a593Smuzhiyun # unset the internal Build object to prevent it being reused for the 1981*4882a593Smuzhiyun # next build 1982*4882a593Smuzhiyun self.internal_state['build'] = None 1983