blob: 93979054d546e378b8144e5bbc8dc76b21de8ef2 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#
2# BitBake ToasterUI Implementation
3#
4# Copyright (C) 2013 Intel Corporation
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License version 2 as
8# published by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19import sys
20import bb
21import re
22import os
23
24os.environ["DJANGO_SETTINGS_MODULE"] = "toaster.toastermain.settings"
25
26
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050027import django
Patrick Williamsc124f4f2015-09-15 14:41:29 -050028from django.utils import timezone
29
30
31def _configure_toaster():
32 """ Add toaster to sys path for importing modules
33 """
34 sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'toaster'))
35_configure_toaster()
36
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050037django.setup()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050038
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050039from orm.models import Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage, HelpText
40from orm.models import Target_Image_File, BuildArtifact
41from orm.models import Variable, VariableHistory
42from orm.models import Package, Package_File, Target_Installed_Package, Target_File
43from orm.models import Task_Dependency, Package_Dependency
44from orm.models import Recipe_Dependency, Provides
45from orm.models import Project, CustomImagePackage, CustomImageRecipe
46
Patrick Williamsc124f4f2015-09-15 14:41:29 -050047from bldcontrol.models import BuildEnvironment, BuildRequest
48
49from bb.msg import BBLogFormatter as formatter
50from django.db import models
51from pprint import pformat
52import logging
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050053from datetime import datetime, timedelta
Patrick Williamsc124f4f2015-09-15 14:41:29 -050054
55from django.db import transaction, connection
56
57# pylint: disable=invalid-name
58# the logger name is standard throughout BitBake
59logger = logging.getLogger("ToasterLogger")
60
61
62class NotExisting(Exception):
63 pass
64
65class ORMWrapper(object):
66 """ This class creates the dictionaries needed to store information in the database
67 following the format defined by the Django models. It is also used to save this
68 information in the database.
69 """
70
71 def __init__(self):
72 self.layer_version_objects = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -050073 self.layer_version_built = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -050074 self.task_objects = {}
75 self.recipe_objects = {}
76
77 @staticmethod
78 def _build_key(**kwargs):
79 key = "0"
80 for k in sorted(kwargs.keys()):
81 if isinstance(kwargs[k], models.Model):
82 key += "-%d" % kwargs[k].id
83 else:
84 key += "-%s" % str(kwargs[k])
85 return key
86
87
88 def _cached_get_or_create(self, clazz, **kwargs):
89 """ This is a memory-cached get_or_create. We assume that the objects will not be created in the
90 database through any other means.
91 """
92
93 assert issubclass(clazz, models.Model), "_cached_get_or_create needs to get the class as first argument"
94
95 key = ORMWrapper._build_key(**kwargs)
96 dictname = "objects_%s" % clazz.__name__
97 if not dictname in vars(self).keys():
98 vars(self)[dictname] = {}
99
100 created = False
101 if not key in vars(self)[dictname].keys():
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500102 vars(self)[dictname][key], created = \
103 clazz.objects.get_or_create(**kwargs)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500104
105 return (vars(self)[dictname][key], created)
106
107
108 def _cached_get(self, clazz, **kwargs):
109 """ This is a memory-cached get. We assume that the objects will not change in the database between gets.
110 """
111 assert issubclass(clazz, models.Model), "_cached_get needs to get the class as first argument"
112
113 key = ORMWrapper._build_key(**kwargs)
114 dictname = "objects_%s" % clazz.__name__
115
116 if not dictname in vars(self).keys():
117 vars(self)[dictname] = {}
118
119 if not key in vars(self)[dictname].keys():
120 vars(self)[dictname][key] = clazz.objects.get(**kwargs)
121
122 return vars(self)[dictname][key]
123
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500124 def _timestamp_to_datetime(self, secs):
125 """
126 Convert timestamp in seconds to Python datetime
127 """
128 return datetime(1970, 1, 1) + timedelta(seconds=secs)
129
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500130 # pylint: disable=no-self-use
131 # we disable detection of no self use in functions because the methods actually work on the object
132 # even if they don't touch self anywhere
133
134 # pylint: disable=bad-continuation
135 # we do not follow the python conventions for continuation indentation due to long lines here
136
137 def create_build_object(self, build_info, brbe, project_id):
138 assert 'machine' in build_info
139 assert 'distro' in build_info
140 assert 'distro_version' in build_info
141 assert 'started_on' in build_info
142 assert 'cooker_log_path' in build_info
143 assert 'build_name' in build_info
144 assert 'bitbake_version' in build_info
145
146 prj = None
147 buildrequest = None
148 if brbe is not None: # this build was triggered by a request from a user
149 logger.debug(1, "buildinfohelper: brbe is %s" % brbe)
150 br, _ = brbe.split(":")
151 buildrequest = BuildRequest.objects.get(pk = br)
152 prj = buildrequest.project
153
154 elif project_id is not None: # this build was triggered by an external system for a specific project
155 logger.debug(1, "buildinfohelper: project is %s" % prj)
156 prj = Project.objects.get(pk = project_id)
157
158 else: # this build was triggered by a legacy system, or command line interactive mode
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500159 prj = Project.objects.get_or_create_default_project()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500160 logger.debug(1, "buildinfohelper: project is not specified, defaulting to %s" % prj)
161
162
163 if buildrequest is not None:
164 build = buildrequest.build
165 logger.info("Updating existing build, with %s", build_info)
166 build.project = prj
167 build.machine=build_info['machine']
168 build.distro=build_info['distro']
169 build.distro_version=build_info['distro_version']
170 build.cooker_log_path=build_info['cooker_log_path']
171 build.build_name=build_info['build_name']
172 build.bitbake_version=build_info['bitbake_version']
173 build.save()
174
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500175 else:
176 build = Build.objects.create(
177 project = prj,
178 machine=build_info['machine'],
179 distro=build_info['distro'],
180 distro_version=build_info['distro_version'],
181 started_on=build_info['started_on'],
182 completed_on=build_info['started_on'],
183 cooker_log_path=build_info['cooker_log_path'],
184 build_name=build_info['build_name'],
185 bitbake_version=build_info['bitbake_version'])
186
187 logger.debug(1, "buildinfohelper: build is created %s" % build)
188
189 if buildrequest is not None:
190 buildrequest.build = build
191 buildrequest.save()
192
193 return build
194
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500195 @staticmethod
196 def get_or_create_targets(target_info):
197 result = []
198 for target in target_info['targets']:
199 task = ''
200 if ':' in target:
201 target, task = target.split(':', 1)
202 if task.startswith('do_'):
203 task = task[3:]
204 if task == 'build':
205 task = ''
206 obj, created = Target.objects.get_or_create(build=target_info['build'],
207 target=target)
208 if created:
209 obj.is_image = False
210 if task:
211 obj.task = task
212 obj.save()
213 result.append(obj)
214 return result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500215
216 def update_build_object(self, build, errors, warnings, taskfailures):
217 assert isinstance(build,Build)
218 assert isinstance(errors, int)
219 assert isinstance(warnings, int)
220
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500221 if build.outcome == Build.CANCELLED:
222 return
223 try:
224 if build.buildrequest.state == BuildRequest.REQ_CANCELLING:
225 return
226 except AttributeError:
227 # We may not have a buildrequest if this is a command line build
228 pass
229
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500230 outcome = Build.SUCCEEDED
231 if errors or taskfailures:
232 outcome = Build.FAILED
233
234 build.completed_on = timezone.now()
235 build.outcome = outcome
236 build.save()
237
238 def update_target_set_license_manifest(self, target, license_manifest_path):
239 target.license_manifest_path = license_manifest_path
240 target.save()
241
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500242 def update_task_object(self, build, task_name, recipe_name, task_stats):
243 """
244 Find the task for build which matches the recipe and task name
245 to be stored
246 """
247 task_to_update = Task.objects.get(
248 build = build,
249 task_name = task_name,
250 recipe__name = recipe_name
251 )
252
253 if 'started' in task_stats and 'ended' in task_stats:
254 task_to_update.started = self._timestamp_to_datetime(task_stats['started'])
255 task_to_update.ended = self._timestamp_to_datetime(task_stats['ended'])
256 task_to_update.elapsed_time = (task_stats['ended'] - task_stats['started'])
257 task_to_update.cpu_time_user = task_stats.get('cpu_time_user')
258 task_to_update.cpu_time_system = task_stats.get('cpu_time_system')
259 if 'disk_io_read' in task_stats and 'disk_io_write' in task_stats:
260 task_to_update.disk_io_read = task_stats['disk_io_read']
261 task_to_update.disk_io_write = task_stats['disk_io_write']
262 task_to_update.disk_io = task_stats['disk_io_read'] + task_stats['disk_io_write']
263
264 task_to_update.save()
265
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500266 def get_update_task_object(self, task_information, must_exist = False):
267 assert 'build' in task_information
268 assert 'recipe' in task_information
269 assert 'task_name' in task_information
270
271 # we use must_exist info for database look-up optimization
272 task_object, created = self._cached_get_or_create(Task,
273 build=task_information['build'],
274 recipe=task_information['recipe'],
275 task_name=task_information['task_name']
276 )
277 if created and must_exist:
278 task_information['debug'] = "build id %d, recipe id %d" % (task_information['build'].pk, task_information['recipe'].pk)
279 raise NotExisting("Task object created when expected to exist", task_information)
280
281 object_changed = False
282 for v in vars(task_object):
283 if v in task_information.keys():
284 if vars(task_object)[v] != task_information[v]:
285 vars(task_object)[v] = task_information[v]
286 object_changed = True
287
288 # update setscene-related information if the task has a setscene
289 if task_object.outcome == Task.OUTCOME_COVERED and 1 == task_object.get_related_setscene().count():
290 task_object.outcome = Task.OUTCOME_CACHED
291 object_changed = True
292
293 outcome_task_setscene = Task.objects.get(task_executed=True, build = task_object.build,
294 recipe = task_object.recipe, task_name=task_object.task_name+"_setscene").outcome
295 if outcome_task_setscene == Task.OUTCOME_SUCCESS:
296 task_object.sstate_result = Task.SSTATE_RESTORED
297 object_changed = True
298 elif outcome_task_setscene == Task.OUTCOME_FAILED:
299 task_object.sstate_result = Task.SSTATE_FAILED
300 object_changed = True
301
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500302 if object_changed:
303 task_object.save()
304 return task_object
305
306
307 def get_update_recipe_object(self, recipe_information, must_exist = False):
308 assert 'layer_version' in recipe_information
309 assert 'file_path' in recipe_information
310 assert 'pathflags' in recipe_information
311
312 assert not recipe_information['file_path'].startswith("/") # we should have layer-relative paths at all times
313
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500314
315 def update_recipe_obj(recipe_object):
316 object_changed = False
317 for v in vars(recipe_object):
318 if v in recipe_information.keys():
319 object_changed = True
320 vars(recipe_object)[v] = recipe_information[v]
321
322 if object_changed:
323 recipe_object.save()
324
325 recipe, created = self._cached_get_or_create(Recipe, layer_version=recipe_information['layer_version'],
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500326 file_path=recipe_information['file_path'], pathflags = recipe_information['pathflags'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500327
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500328 update_recipe_obj(recipe)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500329
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500330 built_recipe = None
331 # Create a copy of the recipe for historical puposes and update it
332 for built_layer in self.layer_version_built:
333 if built_layer.layer == recipe_information['layer_version'].layer:
334 built_recipe, c = self._cached_get_or_create(Recipe,
335 layer_version=built_layer,
336 file_path=recipe_information['file_path'],
337 pathflags = recipe_information['pathflags'])
338 update_recipe_obj(built_recipe)
339 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500340
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500341
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500342 # If we're in analysis mode or if this is a custom recipe
343 # then we are wholly responsible for the data
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500344 # and therefore we return the 'real' recipe rather than the build
345 # history copy of the recipe.
346 if recipe_information['layer_version'].build is not None and \
347 recipe_information['layer_version'].build.project == \
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500348 Project.objects.get_or_create_default_project():
349 return recipe
350
351 if built_recipe is None:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500352 return recipe
353
354 return built_recipe
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500355
356 def get_update_layer_version_object(self, build_obj, layer_obj, layer_version_information):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500357 if isinstance(layer_obj, Layer_Version):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500358 # Special case the toaster-custom-images layer which is created
359 # on the fly so don't update the values which may cause the layer
360 # to be duplicated on a future get_or_create
361 if layer_obj.layer.name == CustomImageRecipe.LAYER_NAME:
362 return layer_obj
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500363 # We already found our layer version for this build so just
364 # update it with the new build information
365 logger.debug("We found our layer from toaster")
366 layer_obj.local_path = layer_version_information['local_path']
367 layer_obj.save()
368 self.layer_version_objects.append(layer_obj)
369
370 # create a new copy of this layer version as a snapshot for
371 # historical purposes
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500372 layer_copy, c = Layer_Version.objects.get_or_create(
373 build=build_obj,
374 layer=layer_obj.layer,
375 up_branch=layer_obj.up_branch,
376 branch=layer_version_information['branch'],
377 commit=layer_version_information['commit'],
378 local_path=layer_version_information['local_path'],
379 )
380
381 logger.info("created new historical layer version %d",
382 layer_copy.pk)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500383
384 self.layer_version_built.append(layer_copy)
385
386 return layer_obj
387
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500388 assert isinstance(build_obj, Build)
389 assert isinstance(layer_obj, Layer)
390 assert 'branch' in layer_version_information
391 assert 'commit' in layer_version_information
392 assert 'priority' in layer_version_information
393 assert 'local_path' in layer_version_information
394
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500395 # If we're doing a command line build then associate this new layer with the
396 # project to avoid it 'contaminating' toaster data
397 project = None
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500398 if build_obj.project == Project.objects.get_or_create_default_project():
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500399 project = build_obj.project
400
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500401 layer_version_object, _ = Layer_Version.objects.get_or_create(
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500402 build = build_obj,
403 layer = layer_obj,
404 branch = layer_version_information['branch'],
405 commit = layer_version_information['commit'],
406 priority = layer_version_information['priority'],
407 local_path = layer_version_information['local_path'],
408 project=project)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500409
410 self.layer_version_objects.append(layer_version_object)
411
412 return layer_version_object
413
414 def get_update_layer_object(self, layer_information, brbe):
415 assert 'name' in layer_information
416 assert 'layer_index_url' in layer_information
417
418 if brbe is None:
419 layer_object, _ = Layer.objects.get_or_create(
420 name=layer_information['name'],
421 layer_index_url=layer_information['layer_index_url'])
422 return layer_object
423 else:
424 # we are under managed mode; we must match the layer used in the Project Layer
425 br_id, be_id = brbe.split(":")
426
427 # find layer by checkout path;
428 from bldcontrol import bbcontroller
429 bc = bbcontroller.getBuildEnvironmentController(pk = be_id)
430
431 # we might have a race condition here, as the project layers may change between the build trigger and the actual build execution
432 # but we can only match on the layer name, so the worst thing can happen is a mis-identification of the layer, not a total failure
433
434 # note that this is different
435 buildrequest = BuildRequest.objects.get(pk = br_id)
436 for brl in buildrequest.brlayer_set.all():
437 localdirname = os.path.join(bc.getGitCloneDirectory(brl.giturl, brl.commit), brl.dirpath)
438 # we get a relative path, unless running in HEAD mode where the path is absolute
439 if not localdirname.startswith("/"):
440 localdirname = os.path.join(bc.be.sourcedir, localdirname)
441 #logger.debug(1, "Localdirname %s lcal_path %s" % (localdirname, layer_information['local_path']))
442 if localdirname.startswith(layer_information['local_path']):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500443 # If the build request came from toaster this field
444 # should contain the information from the layer_version
445 # That created this build request.
446 if brl.layer_version:
447 return brl.layer_version
448
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500449 # we matched the BRLayer, but we need the layer_version that generated this BR; reverse of the Project.schedule_build()
450 #logger.debug(1, "Matched %s to BRlayer %s" % (pformat(layer_information["local_path"]), localdirname))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500451
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500452 for pl in buildrequest.project.projectlayer_set.filter(layercommit__layer__name = brl.name):
453 if pl.layercommit.layer.vcs_url == brl.giturl :
454 layer = pl.layercommit.layer
455 layer.save()
456 return layer
457
458 raise NotExisting("Unidentified layer %s" % pformat(layer_information))
459
460
461 def save_target_file_information(self, build_obj, target_obj, filedata):
462 assert isinstance(build_obj, Build)
463 assert isinstance(target_obj, Target)
464 dirs = filedata['dirs']
465 files = filedata['files']
466 syms = filedata['syms']
467
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500468 # always create the root directory as a special case;
469 # note that this is never displayed, so the owner, group,
470 # size, permission are irrelevant
471 tf_obj = Target_File.objects.create(target = target_obj,
472 path = '/',
473 size = 0,
474 owner = '',
475 group = '',
476 permission = '',
477 inodetype = Target_File.ITYPE_DIRECTORY)
478 tf_obj.save()
479
480 # insert directories, ordered by name depth
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500481 for d in sorted(dirs, key=lambda x:len(x[-1].split("/"))):
482 (user, group, size) = d[1:4]
483 permission = d[0][1:]
484 path = d[4].lstrip(".")
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500485
486 # we already created the root directory, so ignore any
487 # entry for it
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500488 if len(path) == 0:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500489 continue
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500490
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500491 parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1])
492 if len(parent_path) == 0:
493 parent_path = "/"
494 parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY)
495 tf_obj = Target_File.objects.create(
496 target = target_obj,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500497 path = unicode(path, 'utf-8'),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500498 size = size,
499 inodetype = Target_File.ITYPE_DIRECTORY,
500 permission = permission,
501 owner = user,
502 group = group,
503 directory = parent_obj)
504
505
506 # we insert files
507 for d in files:
508 (user, group, size) = d[1:4]
509 permission = d[0][1:]
510 path = d[4].lstrip(".")
511 parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1])
512 inodetype = Target_File.ITYPE_REGULAR
513 if d[0].startswith('b'):
514 inodetype = Target_File.ITYPE_BLOCK
515 if d[0].startswith('c'):
516 inodetype = Target_File.ITYPE_CHARACTER
517 if d[0].startswith('p'):
518 inodetype = Target_File.ITYPE_FIFO
519
520 tf_obj = Target_File.objects.create(
521 target = target_obj,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500522 path = unicode(path, 'utf-8'),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500523 size = size,
524 inodetype = inodetype,
525 permission = permission,
526 owner = user,
527 group = group)
528 parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY)
529 tf_obj.directory = parent_obj
530 tf_obj.save()
531
532 # we insert symlinks
533 for d in syms:
534 (user, group, size) = d[1:4]
535 permission = d[0][1:]
536 path = d[4].lstrip(".")
537 filetarget_path = d[6]
538
539 parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1])
540 if not filetarget_path.startswith("/"):
541 # we have a relative path, get a normalized absolute one
542 filetarget_path = parent_path + "/" + filetarget_path
543 fcp = filetarget_path.split("/")
544 fcpl = []
545 for i in fcp:
546 if i == "..":
547 fcpl.pop()
548 else:
549 fcpl.append(i)
550 filetarget_path = "/".join(fcpl)
551
552 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500553 filetarget_obj = Target_File.objects.get(
554 target = target_obj,
555 path = unicode(filetarget_path, 'utf-8'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500556 except Target_File.DoesNotExist:
557 # we might have an invalid link; no way to detect this. just set it to None
558 filetarget_obj = None
559
560 parent_obj = Target_File.objects.get(target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY)
561
562 tf_obj = Target_File.objects.create(
563 target = target_obj,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500564 path = unicode(path, 'utf-8'),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500565 size = size,
566 inodetype = Target_File.ITYPE_SYMLINK,
567 permission = permission,
568 owner = user,
569 group = group,
570 directory = parent_obj,
571 sym_target = filetarget_obj)
572
573
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500574 def save_target_package_information(self, build_obj, target_obj, packagedict, pkgpnmap, recipes, built_package=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500575 assert isinstance(build_obj, Build)
576 assert isinstance(target_obj, Target)
577
578 errormsg = ""
579 for p in packagedict:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500580 # Search name swtiches round the installed name vs package name
581 # by default installed name == package name
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500582 searchname = p
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500583 if p not in pkgpnmap:
584 logger.warning("Image packages list contains %p, but is"
585 " missing from all packages list where the"
586 " metadata comes from. Skipping...", p)
587 continue
588
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500589 if 'OPKGN' in pkgpnmap[p].keys():
590 searchname = pkgpnmap[p]['OPKGN']
591
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500592 built_recipe = recipes[pkgpnmap[p]['PN']]
593
594 if built_package:
595 packagedict[p]['object'], created = Package.objects.get_or_create( build = build_obj, name = searchname )
596 recipe = built_recipe
597 else:
598 packagedict[p]['object'], created = \
599 CustomImagePackage.objects.get_or_create(name=searchname)
600 # Clear the Package_Dependency objects as we're going to update
601 # the CustomImagePackage with the latest dependency information
602 packagedict[p]['object'].package_dependencies_target.all().delete()
603 packagedict[p]['object'].package_dependencies_source.all().delete()
604 try:
605 recipe = self._cached_get(
606 Recipe,
607 name=built_recipe.name,
608 layer_version__build=None,
609 layer_version__up_branch=
610 built_recipe.layer_version.up_branch,
611 file_path=built_recipe.file_path,
612 version=built_recipe.version
613 )
614 except (Recipe.DoesNotExist,
615 Recipe.MultipleObjectsReturned) as e:
616 logger.info("We did not find one recipe for the"
617 "configuration data package %s %s" % (p, e))
618 continue
619
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500620 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]
621 # fill in everything we can from the runtime-reverse package data
622 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500623 packagedict[p]['object'].recipe = recipe
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500624 packagedict[p]['object'].version = pkgpnmap[p]['PV']
625 packagedict[p]['object'].installed_name = p
626 packagedict[p]['object'].revision = pkgpnmap[p]['PR']
627 packagedict[p]['object'].license = pkgpnmap[p]['LICENSE']
628 packagedict[p]['object'].section = pkgpnmap[p]['SECTION']
629 packagedict[p]['object'].summary = pkgpnmap[p]['SUMMARY']
630 packagedict[p]['object'].description = pkgpnmap[p]['DESCRIPTION']
631 packagedict[p]['object'].size = int(pkgpnmap[p]['PKGSIZE'])
632
633 # no files recorded for this package, so save files info
634 packagefile_objects = []
635 for targetpath in pkgpnmap[p]['FILES_INFO']:
636 targetfilesize = pkgpnmap[p]['FILES_INFO'][targetpath]
637 packagefile_objects.append(Package_File( package = packagedict[p]['object'],
638 path = targetpath,
639 size = targetfilesize))
640 if len(packagefile_objects):
641 Package_File.objects.bulk_create(packagefile_objects)
642 except KeyError as e:
643 errormsg += " stpi: Key error, package %s key %s \n" % ( p, e )
644
645 # save disk installed size
646 packagedict[p]['object'].installed_size = packagedict[p]['size']
647 packagedict[p]['object'].save()
648
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500649 if built_package:
650 Target_Installed_Package.objects.create(target = target_obj, package = packagedict[p]['object'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500651
652 packagedeps_objs = []
653 for p in packagedict:
654 for (px,deptype) in packagedict[p]['depends']:
655 if deptype == 'depends':
656 tdeptype = Package_Dependency.TYPE_TRDEPENDS
657 elif deptype == 'recommends':
658 tdeptype = Package_Dependency.TYPE_TRECOMMENDS
659
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500660 try:
661 packagedeps_objs.append(Package_Dependency(
662 package = packagedict[p]['object'],
663 depends_on = packagedict[px]['object'],
664 dep_type = tdeptype,
665 target = target_obj))
666 except KeyError as e:
667 logger.warn("Could not add dependency to the package %s "
668 "because %s is an unknown package", p, px)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500669
670 if len(packagedeps_objs) > 0:
671 Package_Dependency.objects.bulk_create(packagedeps_objs)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500672 else:
673 logger.info("No package dependencies created")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500674
675 if len(errormsg) > 0:
676 logger.warn("buildinfohelper: target_package_info could not identify recipes: \n%s", errormsg)
677
678 def save_target_image_file_information(self, target_obj, file_name, file_size):
679 Target_Image_File.objects.create( target = target_obj,
680 file_name = file_name,
681 file_size = file_size)
682
683 def save_artifact_information(self, build_obj, file_name, file_size):
684 # we skip the image files from other builds
685 if Target_Image_File.objects.filter(file_name = file_name).count() > 0:
686 return
687
688 # do not update artifacts found in other builds
689 if BuildArtifact.objects.filter(file_name = file_name).count() > 0:
690 return
691
692 BuildArtifact.objects.create(build = build_obj, file_name = file_name, file_size = file_size)
693
694 def create_logmessage(self, log_information):
695 assert 'build' in log_information
696 assert 'level' in log_information
697 assert 'message' in log_information
698
699 log_object = LogMessage.objects.create(
700 build = log_information['build'],
701 level = log_information['level'],
702 message = log_information['message'])
703
704 for v in vars(log_object):
705 if v in log_information.keys():
706 vars(log_object)[v] = log_information[v]
707
708 return log_object.save()
709
710
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500711 def save_build_package_information(self, build_obj, package_info, recipes,
712 built_package):
713 # assert isinstance(build_obj, Build)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500714
715 # create and save the object
716 pname = package_info['PKG']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500717 built_recipe = recipes[package_info['PN']]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500718 if 'OPKGN' in package_info.keys():
719 pname = package_info['OPKGN']
720
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500721 if built_package:
722 bp_object, _ = Package.objects.get_or_create( build = build_obj,
723 name = pname )
724 recipe = built_recipe
725 else:
726 bp_object, created = \
727 CustomImagePackage.objects.get_or_create(name=pname)
728 try:
729 recipe = self._cached_get(Recipe,
730 name=built_recipe.name,
731 layer_version__build=None,
732 file_path=built_recipe.file_path,
733 version=built_recipe.version)
734
735 except (Recipe.DoesNotExist, Recipe.MultipleObjectsReturned):
736 logger.debug("We did not find one recipe for the configuration"
737 "data package %s" % pname)
738 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500739
740 bp_object.installed_name = package_info['PKG']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500741 bp_object.recipe = recipe
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500742 bp_object.version = package_info['PKGV']
743 bp_object.revision = package_info['PKGR']
744 bp_object.summary = package_info['SUMMARY']
745 bp_object.description = package_info['DESCRIPTION']
746 bp_object.size = int(package_info['PKGSIZE'])
747 bp_object.section = package_info['SECTION']
748 bp_object.license = package_info['LICENSE']
749 bp_object.save()
750
751 # save any attached file information
752 packagefile_objects = []
753 for path in package_info['FILES_INFO']:
754 packagefile_objects.append(Package_File( package = bp_object,
755 path = path,
756 size = package_info['FILES_INFO'][path] ))
757 if len(packagefile_objects):
758 Package_File.objects.bulk_create(packagefile_objects)
759
760 def _po_byname(p):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500761 if built_package:
762 pkg, created = Package.objects.get_or_create(build=build_obj,
763 name=p)
764 else:
765 pkg, created = CustomImagePackage.objects.get_or_create(name=p)
766
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500767 if created:
768 pkg.size = -1
769 pkg.save()
770 return pkg
771
772 packagedeps_objs = []
773 # save soft dependency information
774 if 'RDEPENDS' in package_info and package_info['RDEPENDS']:
775 for p in bb.utils.explode_deps(package_info['RDEPENDS']):
776 packagedeps_objs.append(Package_Dependency( package = bp_object,
777 depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RDEPENDS))
778 if 'RPROVIDES' in package_info and package_info['RPROVIDES']:
779 for p in bb.utils.explode_deps(package_info['RPROVIDES']):
780 packagedeps_objs.append(Package_Dependency( package = bp_object,
781 depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RPROVIDES))
782 if 'RRECOMMENDS' in package_info and package_info['RRECOMMENDS']:
783 for p in bb.utils.explode_deps(package_info['RRECOMMENDS']):
784 packagedeps_objs.append(Package_Dependency( package = bp_object,
785 depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RRECOMMENDS))
786 if 'RSUGGESTS' in package_info and package_info['RSUGGESTS']:
787 for p in bb.utils.explode_deps(package_info['RSUGGESTS']):
788 packagedeps_objs.append(Package_Dependency( package = bp_object,
789 depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RSUGGESTS))
790 if 'RREPLACES' in package_info and package_info['RREPLACES']:
791 for p in bb.utils.explode_deps(package_info['RREPLACES']):
792 packagedeps_objs.append(Package_Dependency( package = bp_object,
793 depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RREPLACES))
794 if 'RCONFLICTS' in package_info and package_info['RCONFLICTS']:
795 for p in bb.utils.explode_deps(package_info['RCONFLICTS']):
796 packagedeps_objs.append(Package_Dependency( package = bp_object,
797 depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RCONFLICTS))
798
799 if len(packagedeps_objs) > 0:
800 Package_Dependency.objects.bulk_create(packagedeps_objs)
801
802 return bp_object
803
804 def save_build_variables(self, build_obj, vardump):
805 assert isinstance(build_obj, Build)
806
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500807 for k in vardump:
808 desc = vardump[k]['doc']
809 if desc is None:
810 var_words = [word for word in k.split('_')]
811 root_var = "_".join([word for word in var_words if word.isupper()])
812 if root_var and root_var != k and root_var in vardump:
813 desc = vardump[root_var]['doc']
814 if desc is None:
815 desc = ''
816 if len(desc):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500817 HelpText.objects.get_or_create(build=build_obj,
818 area=HelpText.VARIABLE,
819 key=k, text=desc)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500820 if not bool(vardump[k]['func']):
821 value = vardump[k]['v']
822 if value is None:
823 value = ''
824 variable_obj = Variable.objects.create( build = build_obj,
825 variable_name = k,
826 variable_value = value,
827 description = desc)
828
829 varhist_objects = []
830 for vh in vardump[k]['history']:
831 if not 'documentation.conf' in vh['file']:
832 varhist_objects.append(VariableHistory( variable = variable_obj,
833 file_name = vh['file'],
834 line_number = vh['line'],
835 operation = vh['op']))
836 if len(varhist_objects):
837 VariableHistory.objects.bulk_create(varhist_objects)
838
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500839
840class MockEvent(object):
841 """ This object is used to create event, for which normal event-processing methods can
842 be used, out of data that is not coming via an actual event
843 """
844 def __init__(self):
845 self.msg = None
846 self.levelno = None
847 self.taskname = None
848 self.taskhash = None
849 self.pathname = None
850 self.lineno = None
851
852
853class BuildInfoHelper(object):
854 """ This class gathers the build information from the server and sends it
855 towards the ORM wrapper for storing in the database
856 It is instantiated once per build
857 Keeps in memory all data that needs matching before writing it to the database
858 """
859
860 # pylint: disable=protected-access
861 # the code will look into the protected variables of the event; no easy way around this
862 # pylint: disable=bad-continuation
863 # we do not follow the python conventions for continuation indentation due to long lines here
864
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500865 def __init__(self, server, has_build_history = False, brbe = None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500866 self.internal_state = {}
867 self.internal_state['taskdata'] = {}
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500868 self.internal_state['targets'] = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500869 self.task_order = 0
870 self.autocommit_step = 1
871 self.server = server
872 # we use manual transactions if the database doesn't autocommit on us
873 if not connection.features.autocommits_when_autocommit_is_off:
874 transaction.set_autocommit(False)
875 self.orm_wrapper = ORMWrapper()
876 self.has_build_history = has_build_history
877 self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500878
879 # this is set for Toaster-triggered builds by localhostbecontroller
880 # via toasterui
881 self.brbe = brbe
882
883 self.project = None
884
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500885 logger.debug(1, "buildinfohelper: Build info helper inited %s" % vars(self))
886
887
888 ###################
889 ## methods to convert event/external info into objects that the ORM layer uses
890
891
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500892 def _get_build_information(self, build_log_path):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500893 build_info = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500894 build_info['machine'] = self.server.runCommand(["getVariable", "MACHINE"])[0]
895 build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0]
896 build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0]
897 build_info['started_on'] = timezone.now()
898 build_info['completed_on'] = timezone.now()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500899 build_info['cooker_log_path'] = build_log_path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500900 build_info['build_name'] = self.server.runCommand(["getVariable", "BUILDNAME"])[0]
901 build_info['bitbake_version'] = self.server.runCommand(["getVariable", "BB_VERSION"])[0]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500902 build_info['project'] = self.project = self.server.runCommand(["getVariable", "TOASTER_PROJECT"])[0]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500903 return build_info
904
905 def _get_task_information(self, event, recipe):
906 assert 'taskname' in vars(event)
907
908 task_information = {}
909 task_information['build'] = self.internal_state['build']
910 task_information['outcome'] = Task.OUTCOME_NA
911 task_information['recipe'] = recipe
912 task_information['task_name'] = event.taskname
913 try:
914 # some tasks don't come with a hash. and that's ok
915 task_information['sstate_checksum'] = event.taskhash
916 except AttributeError:
917 pass
918 return task_information
919
920 def _get_layer_version_for_path(self, path):
921 assert path.startswith("/")
922 assert 'build' in self.internal_state
923
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500924 def _slkey_interactive(layer_version):
925 assert isinstance(layer_version, Layer_Version)
926 return len(layer_version.local_path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500927
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500928 # Heuristics: we always match recipe to the deepest layer path in the discovered layers
929 for lvo in sorted(self.orm_wrapper.layer_version_objects, reverse=True, key=_slkey_interactive):
930 # we can match to the recipe file path
931 if path.startswith(lvo.local_path):
932 return lvo
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500933
934 #if we get here, we didn't read layers correctly; dump whatever information we have on the error log
935 logger.warn("Could not match layer version for recipe path %s : %s", path, self.orm_wrapper.layer_version_objects)
936
937 #mockup the new layer
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500938 unknown_layer, _ = Layer.objects.get_or_create(name="Unidentified layer", layer_index_url="")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500939 unknown_layer_version_obj, _ = Layer_Version.objects.get_or_create(layer = unknown_layer, build = self.internal_state['build'])
940
941 # append it so we don't run into this error again and again
942 self.orm_wrapper.layer_version_objects.append(unknown_layer_version_obj)
943
944 return unknown_layer_version_obj
945
946 def _get_recipe_information_from_taskfile(self, taskfile):
947 localfilepath = taskfile.split(":")[-1]
948 filepath_flags = ":".join(sorted(taskfile.split(":")[:-1]))
949 layer_version_obj = self._get_layer_version_for_path(localfilepath)
950
951
952
953 recipe_info = {}
954 recipe_info['layer_version'] = layer_version_obj
955 recipe_info['file_path'] = localfilepath
956 recipe_info['pathflags'] = filepath_flags
957
958 if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path):
959 recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/")
960 else:
961 raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path))
962
963 return recipe_info
964
965 def _get_path_information(self, task_object):
966 assert isinstance(task_object, Task)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500967 build_stats_format = "{tmpdir}/buildstats/{buildname}/{package}/"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500968 build_stats_path = []
969
970 for t in self.internal_state['targets']:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500971 buildname = self.internal_state['build'].build_name
972 pe, pv = task_object.recipe.version.split(":",1)
973 if len(pe) > 0:
974 package = task_object.recipe.name + "-" + pe + "_" + pv
975 else:
976 package = task_object.recipe.name + "-" + pv
977
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500978 build_stats_path.append(build_stats_format.format(tmpdir=self.tmp_dir,
979 buildname=buildname,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500980 package=package))
981
982 return build_stats_path
983
984
985 ################################
986 ## external available methods to store information
987 @staticmethod
988 def _get_data_from_event(event):
989 evdata = None
990 if '_localdata' in vars(event):
991 evdata = event._localdata
992 elif 'data' in vars(event):
993 evdata = event.data
994 else:
995 raise Exception("Event with neither _localdata or data properties")
996 return evdata
997
998 def store_layer_info(self, event):
999 layerinfos = BuildInfoHelper._get_data_from_event(event)
1000 self.internal_state['lvs'] = {}
1001 for layer in layerinfos:
1002 try:
1003 self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)] = layerinfos[layer]['version']
1004 self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)]['local_path'] = layerinfos[layer]['local_path']
1005 except NotExisting as nee:
1006 logger.warn("buildinfohelper: cannot identify layer exception:%s ", nee)
1007
1008
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001009 def store_started_build(self, event, build_log_path):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001010 assert '_pkgs' in vars(event)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001011 build_information = self._get_build_information(build_log_path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001012
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001013 # Update brbe and project as they can be changed for every build
1014 self.project = build_information['project']
1015
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001016 build_obj = self.orm_wrapper.create_build_object(build_information, self.brbe, self.project)
1017
1018 self.internal_state['build'] = build_obj
1019
1020 # save layer version information for this build
1021 if not 'lvs' in self.internal_state:
1022 logger.error("Layer version information not found; Check if the bitbake server was configured to inherit toaster.bbclass.")
1023 else:
1024 for layer_obj in self.internal_state['lvs']:
1025 self.orm_wrapper.get_update_layer_version_object(build_obj, layer_obj, self.internal_state['lvs'][layer_obj])
1026
1027 del self.internal_state['lvs']
1028
1029 # create target information
1030 target_information = {}
1031 target_information['targets'] = event._pkgs
1032 target_information['build'] = build_obj
1033
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001034 self.internal_state['targets'] = self.orm_wrapper.get_or_create_targets(target_information)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001035
1036 # Save build configuration
1037 data = self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0]
1038
1039 # convert the paths from absolute to relative to either the build directory or layer checkouts
1040 path_prefixes = []
1041
1042 if self.brbe is not None:
1043 _, be_id = self.brbe.split(":")
1044 be = BuildEnvironment.objects.get(pk = be_id)
1045 path_prefixes.append(be.builddir)
1046
1047 for layer in sorted(self.orm_wrapper.layer_version_objects, key = lambda x:len(x.local_path), reverse=True):
1048 path_prefixes.append(layer.local_path)
1049
1050 # we strip the prefixes
1051 for k in data:
1052 if not bool(data[k]['func']):
1053 for vh in data[k]['history']:
1054 if not 'documentation.conf' in vh['file']:
1055 abs_file_name = vh['file']
1056 for pp in path_prefixes:
1057 if abs_file_name.startswith(pp + "/"):
1058 vh['file']=abs_file_name[len(pp + "/"):]
1059 break
1060
1061 # save the variables
1062 self.orm_wrapper.save_build_variables(build_obj, data)
1063
1064 return self.brbe
1065
1066
1067 def update_target_image_file(self, event):
1068 evdata = BuildInfoHelper._get_data_from_event(event)
1069
1070 for t in self.internal_state['targets']:
1071 if t.is_image == True:
1072 output_files = list(evdata.viewkeys())
1073 for output in output_files:
1074 if t.target in output and 'rootfs' in output and not output.endswith(".manifest"):
1075 self.orm_wrapper.save_target_image_file_information(t, output, evdata[output])
1076
1077 def update_artifact_image_file(self, event):
1078 evdata = BuildInfoHelper._get_data_from_event(event)
1079 for artifact_path in evdata.keys():
1080 self.orm_wrapper.save_artifact_information(self.internal_state['build'], artifact_path, evdata[artifact_path])
1081
1082 def update_build_information(self, event, errors, warnings, taskfailures):
1083 if 'build' in self.internal_state:
1084 self.orm_wrapper.update_build_object(self.internal_state['build'], errors, warnings, taskfailures)
1085
1086
1087 def store_license_manifest_path(self, event):
1088 deploy_dir = BuildInfoHelper._get_data_from_event(event)['deploy_dir']
1089 image_name = BuildInfoHelper._get_data_from_event(event)['image_name']
1090 path = deploy_dir + "/licenses/" + image_name + "/license.manifest"
1091 for target in self.internal_state['targets']:
1092 if target.target in image_name:
1093 self.orm_wrapper.update_target_set_license_manifest(target, path)
1094
1095
1096 def store_started_task(self, event):
1097 assert isinstance(event, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped))
1098 assert 'taskfile' in vars(event)
1099 localfilepath = event.taskfile.split(":")[-1]
1100 assert localfilepath.startswith("/")
1101
1102 identifier = event.taskfile + ":" + event.taskname
1103
1104 recipe_information = self._get_recipe_information_from_taskfile(event.taskfile)
1105 recipe = self.orm_wrapper.get_update_recipe_object(recipe_information, True)
1106
1107 task_information = self._get_task_information(event, recipe)
1108 task_information['outcome'] = Task.OUTCOME_NA
1109
1110 if isinstance(event, bb.runqueue.runQueueTaskSkipped):
1111 assert 'reason' in vars(event)
1112 task_information['task_executed'] = False
1113 if event.reason == "covered":
1114 task_information['outcome'] = Task.OUTCOME_COVERED
1115 if event.reason == "existing":
1116 task_information['outcome'] = Task.OUTCOME_PREBUILT
1117 else:
1118 task_information['task_executed'] = True
1119 if 'noexec' in vars(event) and event.noexec == True:
1120 task_information['task_executed'] = False
1121 task_information['outcome'] = Task.OUTCOME_EMPTY
1122 task_information['script_type'] = Task.CODING_NA
1123
1124 # do not assign order numbers to scene tasks
1125 if not isinstance(event, bb.runqueue.sceneQueueTaskStarted):
1126 self.task_order += 1
1127 task_information['order'] = self.task_order
1128
1129 self.orm_wrapper.get_update_task_object(task_information)
1130
1131 self.internal_state['taskdata'][identifier] = {
1132 'outcome': task_information['outcome'],
1133 }
1134
1135
1136 def store_tasks_stats(self, event):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001137 task_data = BuildInfoHelper._get_data_from_event(event)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001138
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001139 for (task_file, task_name, task_stats, recipe_name) in task_data:
1140 build = self.internal_state['build']
1141 self.orm_wrapper.update_task_object(build, task_name, recipe_name, task_stats)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001142
1143 def update_and_store_task(self, event):
1144 assert 'taskfile' in vars(event)
1145 localfilepath = event.taskfile.split(":")[-1]
1146 assert localfilepath.startswith("/")
1147
1148 identifier = event.taskfile + ":" + event.taskname
1149 if not identifier in self.internal_state['taskdata']:
1150 if isinstance(event, bb.build.TaskBase):
1151 # we do a bit of guessing
1152 candidates = [x for x in self.internal_state['taskdata'].keys() if x.endswith(identifier)]
1153 if len(candidates) == 1:
1154 identifier = candidates[0]
1155
1156 assert identifier in self.internal_state['taskdata']
1157 identifierlist = identifier.split(":")
1158 realtaskfile = ":".join(identifierlist[0:len(identifierlist)-1])
1159 recipe_information = self._get_recipe_information_from_taskfile(realtaskfile)
1160 recipe = self.orm_wrapper.get_update_recipe_object(recipe_information, True)
1161 task_information = self._get_task_information(event,recipe)
1162
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001163 task_information['outcome'] = self.internal_state['taskdata'][identifier]['outcome']
1164
1165 if 'logfile' in vars(event):
1166 task_information['logfile'] = event.logfile
1167
1168 if '_message' in vars(event):
1169 task_information['message'] = event._message
1170
1171 if 'taskflags' in vars(event):
1172 # with TaskStarted, we get even more information
1173 if 'python' in event.taskflags.keys() and event.taskflags['python'] == '1':
1174 task_information['script_type'] = Task.CODING_PYTHON
1175 else:
1176 task_information['script_type'] = Task.CODING_SHELL
1177
1178 if task_information['outcome'] == Task.OUTCOME_NA:
1179 if isinstance(event, (bb.runqueue.runQueueTaskCompleted, bb.runqueue.sceneQueueTaskCompleted)):
1180 task_information['outcome'] = Task.OUTCOME_SUCCESS
1181 del self.internal_state['taskdata'][identifier]
1182
1183 if isinstance(event, (bb.runqueue.runQueueTaskFailed, bb.runqueue.sceneQueueTaskFailed)):
1184 task_information['outcome'] = Task.OUTCOME_FAILED
1185 del self.internal_state['taskdata'][identifier]
1186
1187 if not connection.features.autocommits_when_autocommit_is_off:
1188 # we force a sync point here, to get the progress bar to show
1189 if self.autocommit_step % 3 == 0:
1190 transaction.set_autocommit(True)
1191 transaction.set_autocommit(False)
1192 self.autocommit_step += 1
1193
1194 self.orm_wrapper.get_update_task_object(task_information, True) # must exist
1195
1196
1197 def store_missed_state_tasks(self, event):
1198 for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['missed']:
1199
1200 # identifier = fn + taskname + "_setscene"
1201 recipe_information = self._get_recipe_information_from_taskfile(fn)
1202 recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
1203 mevent = MockEvent()
1204 mevent.taskname = taskname
1205 mevent.taskhash = taskhash
1206 task_information = self._get_task_information(mevent,recipe)
1207
1208 task_information['start_time'] = timezone.now()
1209 task_information['outcome'] = Task.OUTCOME_NA
1210 task_information['sstate_checksum'] = taskhash
1211 task_information['sstate_result'] = Task.SSTATE_MISS
1212 task_information['path_to_sstate_obj'] = sstatefile
1213
1214 self.orm_wrapper.get_update_task_object(task_information)
1215
1216 for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['found']:
1217
1218 # identifier = fn + taskname + "_setscene"
1219 recipe_information = self._get_recipe_information_from_taskfile(fn)
1220 recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
1221 mevent = MockEvent()
1222 mevent.taskname = taskname
1223 mevent.taskhash = taskhash
1224 task_information = self._get_task_information(mevent,recipe)
1225
1226 task_information['path_to_sstate_obj'] = sstatefile
1227
1228 self.orm_wrapper.get_update_task_object(task_information)
1229
1230
1231 def store_target_package_data(self, event):
1232 # for all image targets
1233 for target in self.internal_state['targets']:
1234 if target.is_image:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001235 pkgdata = BuildInfoHelper._get_data_from_event(event)['pkgdata']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001236 imgdata = BuildInfoHelper._get_data_from_event(event)['imgdata'].get(target.target, {})
1237 filedata = BuildInfoHelper._get_data_from_event(event)['filedata'].get(target.target, {})
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001238
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001239 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001240 self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata, pkgdata, self.internal_state['recipes'], built_package=True)
1241 self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata.copy(), pkgdata, self.internal_state['recipes'], built_package=False)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001242 except KeyError as e:
1243 logger.warn("KeyError in save_target_package_information"
1244 "%s ", e)
1245
1246 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001247 self.orm_wrapper.save_target_file_information(self.internal_state['build'], target, filedata)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001248 except KeyError as e:
1249 logger.warn("KeyError in save_target_file_information"
1250 "%s ", e)
1251
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001252
1253
1254
1255 def store_dependency_information(self, event):
1256 assert '_depgraph' in vars(event)
1257 assert 'layer-priorities' in event._depgraph
1258 assert 'pn' in event._depgraph
1259 assert 'tdepends' in event._depgraph
1260
1261 errormsg = ""
1262
1263 # save layer version priorities
1264 if 'layer-priorities' in event._depgraph.keys():
1265 for lv in event._depgraph['layer-priorities']:
1266 (_, path, _, priority) = lv
1267 layer_version_obj = self._get_layer_version_for_path(path[1:]) # paths start with a ^
1268 assert layer_version_obj is not None
1269 layer_version_obj.priority = priority
1270 layer_version_obj.save()
1271
1272 # save recipe information
1273 self.internal_state['recipes'] = {}
1274 for pn in event._depgraph['pn']:
1275
1276 file_name = event._depgraph['pn'][pn]['filename'].split(":")[-1]
1277 pathflags = ":".join(sorted(event._depgraph['pn'][pn]['filename'].split(":")[:-1]))
1278 layer_version_obj = self._get_layer_version_for_path(file_name)
1279
1280 assert layer_version_obj is not None
1281
1282 recipe_info = {}
1283 recipe_info['name'] = pn
1284 recipe_info['layer_version'] = layer_version_obj
1285
1286 if 'version' in event._depgraph['pn'][pn]:
1287 recipe_info['version'] = event._depgraph['pn'][pn]['version'].lstrip(":")
1288
1289 if 'summary' in event._depgraph['pn'][pn]:
1290 recipe_info['summary'] = event._depgraph['pn'][pn]['summary']
1291
1292 if 'license' in event._depgraph['pn'][pn]:
1293 recipe_info['license'] = event._depgraph['pn'][pn]['license']
1294
1295 if 'description' in event._depgraph['pn'][pn]:
1296 recipe_info['description'] = event._depgraph['pn'][pn]['description']
1297
1298 if 'section' in event._depgraph['pn'][pn]:
1299 recipe_info['section'] = event._depgraph['pn'][pn]['section']
1300
1301 if 'homepage' in event._depgraph['pn'][pn]:
1302 recipe_info['homepage'] = event._depgraph['pn'][pn]['homepage']
1303
1304 if 'bugtracker' in event._depgraph['pn'][pn]:
1305 recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker']
1306
1307 recipe_info['file_path'] = file_name
1308 recipe_info['pathflags'] = pathflags
1309
1310 if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path):
1311 recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/")
1312 else:
1313 raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path))
1314
1315 recipe = self.orm_wrapper.get_update_recipe_object(recipe_info)
1316 recipe.is_image = False
1317 if 'inherits' in event._depgraph['pn'][pn].keys():
1318 for cls in event._depgraph['pn'][pn]['inherits']:
1319 if cls.endswith('/image.bbclass'):
1320 recipe.is_image = True
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001321 recipe_info['is_image'] = True
1322 # Save the is_image state to the relevant recipe objects
1323 self.orm_wrapper.get_update_recipe_object(recipe_info)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001324 break
1325 if recipe.is_image:
1326 for t in self.internal_state['targets']:
1327 if pn == t.target:
1328 t.is_image = True
1329 t.save()
1330 self.internal_state['recipes'][pn] = recipe
1331
1332 # we'll not get recipes for key w/ values listed in ASSUME_PROVIDED
1333
1334 assume_provided = self.server.runCommand(["getVariable", "ASSUME_PROVIDED"])[0].split()
1335
1336 # save recipe dependency
1337 # buildtime
1338 recipedeps_objects = []
1339 for recipe in event._depgraph['depends']:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001340 target = self.internal_state['recipes'][recipe]
1341 for dep in event._depgraph['depends'][recipe]:
1342 if dep in assume_provided:
1343 continue
1344 via = None
1345 if 'providermap' in event._depgraph and dep in event._depgraph['providermap']:
1346 deprecipe = event._depgraph['providermap'][dep][0]
1347 dependency = self.internal_state['recipes'][deprecipe]
1348 via = Provides.objects.get_or_create(name=dep,
1349 recipe=dependency)[0]
1350 elif dep in self.internal_state['recipes']:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001351 dependency = self.internal_state['recipes'][dep]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001352 else:
1353 errormsg += " stpd: KeyError saving recipe dependency for %s, %s \n" % (recipe, dep)
1354 continue
1355 recipe_dep = Recipe_Dependency(recipe=target,
1356 depends_on=dependency,
1357 via=via,
1358 dep_type=Recipe_Dependency.TYPE_DEPENDS)
1359 recipedeps_objects.append(recipe_dep)
1360
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001361 Recipe_Dependency.objects.bulk_create(recipedeps_objects)
1362
1363 # save all task information
1364 def _save_a_task(taskdesc):
1365 spec = re.split(r'\.', taskdesc)
1366 pn = ".".join(spec[0:-1])
1367 taskname = spec[-1]
1368 e = event
1369 e.taskname = pn
1370 recipe = self.internal_state['recipes'][pn]
1371 task_info = self._get_task_information(e, recipe)
1372 task_info['task_name'] = taskname
1373 task_obj = self.orm_wrapper.get_update_task_object(task_info)
1374 return task_obj
1375
1376 # create tasks
1377 tasks = {}
1378 for taskdesc in event._depgraph['tdepends']:
1379 tasks[taskdesc] = _save_a_task(taskdesc)
1380
1381 # create dependencies between tasks
1382 taskdeps_objects = []
1383 for taskdesc in event._depgraph['tdepends']:
1384 target = tasks[taskdesc]
1385 for taskdep in event._depgraph['tdepends'][taskdesc]:
1386 if taskdep not in tasks:
1387 # Fetch tasks info is not collected previously
1388 dep = _save_a_task(taskdep)
1389 else:
1390 dep = tasks[taskdep]
1391 taskdeps_objects.append(Task_Dependency( task = target, depends_on = dep ))
1392 Task_Dependency.objects.bulk_create(taskdeps_objects)
1393
1394 if len(errormsg) > 0:
1395 logger.warn("buildinfohelper: dependency info not identify recipes: \n%s", errormsg)
1396
1397
1398 def store_build_package_information(self, event):
1399 package_info = BuildInfoHelper._get_data_from_event(event)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001400 self.orm_wrapper.save_build_package_information(
1401 self.internal_state['build'],
1402 package_info,
1403 self.internal_state['recipes'],
1404 built_package=True)
1405
1406 self.orm_wrapper.save_build_package_information(
1407 self.internal_state['build'],
1408 package_info,
1409 self.internal_state['recipes'],
1410 built_package=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001411
1412 def _store_build_done(self, errorcode):
1413 logger.info("Build exited with errorcode %d", errorcode)
1414 br_id, be_id = self.brbe.split(":")
1415 be = BuildEnvironment.objects.get(pk = be_id)
1416 be.lock = BuildEnvironment.LOCK_LOCK
1417 be.save()
1418 br = BuildRequest.objects.get(pk = br_id)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001419
1420 # if we're 'done' because we got cancelled update the build outcome
1421 if br.state == BuildRequest.REQ_CANCELLING:
1422 logger.info("Build cancelled")
1423 br.build.outcome = Build.CANCELLED
1424 br.build.save()
1425 self.internal_state['build'] = br.build
1426 errorcode = 0
1427
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001428 if errorcode == 0:
1429 # request archival of the project artifacts
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001430 br.state = BuildRequest.REQ_COMPLETED
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001431 else:
1432 br.state = BuildRequest.REQ_FAILED
1433 br.save()
1434
1435
1436 def store_log_error(self, text):
1437 mockevent = MockEvent()
1438 mockevent.levelno = formatter.ERROR
1439 mockevent.msg = text
1440 mockevent.pathname = '-- None'
1441 mockevent.lineno = LogMessage.ERROR
1442 self.store_log_event(mockevent)
1443
1444 def store_log_exception(self, text, backtrace = ""):
1445 mockevent = MockEvent()
1446 mockevent.levelno = -1
1447 mockevent.msg = text
1448 mockevent.pathname = backtrace
1449 mockevent.lineno = -1
1450 self.store_log_event(mockevent)
1451
1452
1453 def store_log_event(self, event):
1454 if event.levelno < formatter.WARNING:
1455 return
1456
1457 if 'args' in vars(event):
1458 event.msg = event.msg % event.args
1459
1460 if not 'build' in self.internal_state:
1461 if self.brbe is None:
1462 if not 'backlog' in self.internal_state:
1463 self.internal_state['backlog'] = []
1464 self.internal_state['backlog'].append(event)
1465 return
1466 else: # we're under Toaster control, the build is already created
1467 br, _ = self.brbe.split(":")
1468 buildrequest = BuildRequest.objects.get(pk = br)
1469 self.internal_state['build'] = buildrequest.build
1470
1471 if 'build' in self.internal_state and 'backlog' in self.internal_state:
1472 # if we have a backlog of events, do our best to save them here
1473 if len(self.internal_state['backlog']):
1474 tempevent = self.internal_state['backlog'].pop()
1475 logger.debug(1, "buildinfohelper: Saving stored event %s " % tempevent)
1476 self.store_log_event(tempevent)
1477 else:
1478 logger.info("buildinfohelper: All events saved")
1479 del self.internal_state['backlog']
1480
1481 log_information = {}
1482 log_information['build'] = self.internal_state['build']
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001483 if event.levelno == formatter.CRITICAL:
1484 log_information['level'] = LogMessage.CRITICAL
1485 elif event.levelno == formatter.ERROR:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001486 log_information['level'] = LogMessage.ERROR
1487 elif event.levelno == formatter.WARNING:
1488 log_information['level'] = LogMessage.WARNING
1489 elif event.levelno == -2: # toaster self-logging
1490 log_information['level'] = -2
1491 else:
1492 log_information['level'] = LogMessage.INFO
1493
1494 log_information['message'] = event.msg
1495 log_information['pathname'] = event.pathname
1496 log_information['lineno'] = event.lineno
1497 logger.info("Logging error 2: %s", log_information)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001498
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001499 self.orm_wrapper.create_logmessage(log_information)
1500
1501 def close(self, errorcode):
1502 if self.brbe is not None:
1503 self._store_build_done(errorcode)
1504
1505 if 'backlog' in self.internal_state:
1506 if 'build' in self.internal_state:
1507 # we save missed events in the database for the current build
1508 tempevent = self.internal_state['backlog'].pop()
1509 self.store_log_event(tempevent)
1510 else:
1511 # we have no build, and we still have events; something amazingly wrong happend
1512 for event in self.internal_state['backlog']:
1513 logger.error("UNSAVED log: %s", event.msg)
1514
1515 if not connection.features.autocommits_when_autocommit_is_off:
1516 transaction.set_autocommit(True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001517
1518 # unset the brbe; this is to prevent subsequent command-line builds
1519 # being incorrectly attached to the previous Toaster-triggered build;
1520 # see https://bugzilla.yoctoproject.org/show_bug.cgi?id=9021
1521 self.brbe = None