buildinfohelper.py 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521
  1. #
  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. import sys
  19. import bb
  20. import re
  21. import os
  22. os.environ["DJANGO_SETTINGS_MODULE"] = "toaster.toastermain.settings"
  23. import django
  24. from django.utils import timezone
  25. def _configure_toaster():
  26. """ Add toaster to sys path for importing modules
  27. """
  28. sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'toaster'))
  29. _configure_toaster()
  30. django.setup()
  31. from orm.models import Build, Task, Recipe, Layer_Version, Layer, Target, LogMessage, HelpText
  32. from orm.models import Target_Image_File, BuildArtifact
  33. from orm.models import Variable, VariableHistory
  34. from orm.models import Package, Package_File, Target_Installed_Package, Target_File
  35. from orm.models import Task_Dependency, Package_Dependency
  36. from orm.models import Recipe_Dependency, Provides
  37. from orm.models import Project, CustomImagePackage, CustomImageRecipe
  38. from bldcontrol.models import BuildEnvironment, BuildRequest
  39. from bb.msg import BBLogFormatter as formatter
  40. from django.db import models
  41. from pprint import pformat
  42. import logging
  43. from datetime import datetime, timedelta
  44. from django.db import transaction, connection
  45. # pylint: disable=invalid-name
  46. # the logger name is standard throughout BitBake
  47. logger = logging.getLogger("ToasterLogger")
  48. class NotExisting(Exception):
  49. pass
  50. class ORMWrapper(object):
  51. """ This class creates the dictionaries needed to store information in the database
  52. following the format defined by the Django models. It is also used to save this
  53. information in the database.
  54. """
  55. def __init__(self):
  56. self.layer_version_objects = []
  57. self.layer_version_built = []
  58. self.task_objects = {}
  59. self.recipe_objects = {}
  60. @staticmethod
  61. def _build_key(**kwargs):
  62. key = "0"
  63. for k in sorted(kwargs.keys()):
  64. if isinstance(kwargs[k], models.Model):
  65. key += "-%d" % kwargs[k].id
  66. else:
  67. key += "-%s" % str(kwargs[k])
  68. return key
  69. def _cached_get_or_create(self, clazz, **kwargs):
  70. """ This is a memory-cached get_or_create. We assume that the objects will not be created in the
  71. database through any other means.
  72. """
  73. assert issubclass(clazz, models.Model), "_cached_get_or_create needs to get the class as first argument"
  74. key = ORMWrapper._build_key(**kwargs)
  75. dictname = "objects_%s" % clazz.__name__
  76. if not dictname in vars(self).keys():
  77. vars(self)[dictname] = {}
  78. created = False
  79. if not key in vars(self)[dictname].keys():
  80. vars(self)[dictname][key], created = \
  81. clazz.objects.get_or_create(**kwargs)
  82. return (vars(self)[dictname][key], created)
  83. def _cached_get(self, clazz, **kwargs):
  84. """ This is a memory-cached get. We assume that the objects will not change in the database between gets.
  85. """
  86. assert issubclass(clazz, models.Model), "_cached_get needs to get the class as first argument"
  87. key = ORMWrapper._build_key(**kwargs)
  88. dictname = "objects_%s" % clazz.__name__
  89. if not dictname in vars(self).keys():
  90. vars(self)[dictname] = {}
  91. if not key in vars(self)[dictname].keys():
  92. vars(self)[dictname][key] = clazz.objects.get(**kwargs)
  93. return vars(self)[dictname][key]
  94. def _timestamp_to_datetime(self, secs):
  95. """
  96. Convert timestamp in seconds to Python datetime
  97. """
  98. return datetime(1970, 1, 1) + timedelta(seconds=secs)
  99. # pylint: disable=no-self-use
  100. # we disable detection of no self use in functions because the methods actually work on the object
  101. # even if they don't touch self anywhere
  102. # pylint: disable=bad-continuation
  103. # we do not follow the python conventions for continuation indentation due to long lines here
  104. def create_build_object(self, build_info, brbe, project_id):
  105. assert 'machine' in build_info
  106. assert 'distro' in build_info
  107. assert 'distro_version' in build_info
  108. assert 'started_on' in build_info
  109. assert 'cooker_log_path' in build_info
  110. assert 'build_name' in build_info
  111. assert 'bitbake_version' in build_info
  112. prj = None
  113. buildrequest = None
  114. if brbe is not None: # this build was triggered by a request from a user
  115. logger.debug(1, "buildinfohelper: brbe is %s" % brbe)
  116. br, _ = brbe.split(":")
  117. buildrequest = BuildRequest.objects.get(pk = br)
  118. prj = buildrequest.project
  119. elif project_id is not None: # this build was triggered by an external system for a specific project
  120. logger.debug(1, "buildinfohelper: project is %s" % prj)
  121. prj = Project.objects.get(pk = project_id)
  122. else: # this build was triggered by a legacy system, or command line interactive mode
  123. prj = Project.objects.get_or_create_default_project()
  124. logger.debug(1, "buildinfohelper: project is not specified, defaulting to %s" % prj)
  125. if buildrequest is not None:
  126. build = buildrequest.build
  127. logger.info("Updating existing build, with %s", build_info)
  128. build.project = prj
  129. build.machine=build_info['machine']
  130. build.distro=build_info['distro']
  131. build.distro_version=build_info['distro_version']
  132. build.cooker_log_path=build_info['cooker_log_path']
  133. build.build_name=build_info['build_name']
  134. build.bitbake_version=build_info['bitbake_version']
  135. build.save()
  136. else:
  137. build = Build.objects.create(
  138. project = prj,
  139. machine=build_info['machine'],
  140. distro=build_info['distro'],
  141. distro_version=build_info['distro_version'],
  142. started_on=build_info['started_on'],
  143. completed_on=build_info['started_on'],
  144. cooker_log_path=build_info['cooker_log_path'],
  145. build_name=build_info['build_name'],
  146. bitbake_version=build_info['bitbake_version'])
  147. logger.debug(1, "buildinfohelper: build is created %s" % build)
  148. if buildrequest is not None:
  149. buildrequest.build = build
  150. buildrequest.save()
  151. return build
  152. @staticmethod
  153. def get_or_create_targets(target_info):
  154. result = []
  155. for target in target_info['targets']:
  156. task = ''
  157. if ':' in target:
  158. target, task = target.split(':', 1)
  159. if task.startswith('do_'):
  160. task = task[3:]
  161. if task == 'build':
  162. task = ''
  163. obj, created = Target.objects.get_or_create(build=target_info['build'],
  164. target=target)
  165. if created:
  166. obj.is_image = False
  167. if task:
  168. obj.task = task
  169. obj.save()
  170. result.append(obj)
  171. return result
  172. def update_build_object(self, build, errors, warnings, taskfailures):
  173. assert isinstance(build,Build)
  174. assert isinstance(errors, int)
  175. assert isinstance(warnings, int)
  176. if build.outcome == Build.CANCELLED:
  177. return
  178. try:
  179. if build.buildrequest.state == BuildRequest.REQ_CANCELLING:
  180. return
  181. except AttributeError:
  182. # We may not have a buildrequest if this is a command line build
  183. pass
  184. outcome = Build.SUCCEEDED
  185. if errors or taskfailures:
  186. outcome = Build.FAILED
  187. build.completed_on = timezone.now()
  188. build.outcome = outcome
  189. build.save()
  190. def update_target_set_license_manifest(self, target, license_manifest_path):
  191. target.license_manifest_path = license_manifest_path
  192. target.save()
  193. def update_task_object(self, build, task_name, recipe_name, task_stats):
  194. """
  195. Find the task for build which matches the recipe and task name
  196. to be stored
  197. """
  198. task_to_update = Task.objects.get(
  199. build = build,
  200. task_name = task_name,
  201. recipe__name = recipe_name
  202. )
  203. if 'started' in task_stats and 'ended' in task_stats:
  204. task_to_update.started = self._timestamp_to_datetime(task_stats['started'])
  205. task_to_update.ended = self._timestamp_to_datetime(task_stats['ended'])
  206. task_to_update.elapsed_time = (task_stats['ended'] - task_stats['started'])
  207. task_to_update.cpu_time_user = task_stats.get('cpu_time_user')
  208. task_to_update.cpu_time_system = task_stats.get('cpu_time_system')
  209. if 'disk_io_read' in task_stats and 'disk_io_write' in task_stats:
  210. task_to_update.disk_io_read = task_stats['disk_io_read']
  211. task_to_update.disk_io_write = task_stats['disk_io_write']
  212. task_to_update.disk_io = task_stats['disk_io_read'] + task_stats['disk_io_write']
  213. task_to_update.save()
  214. def get_update_task_object(self, task_information, must_exist = False):
  215. assert 'build' in task_information
  216. assert 'recipe' in task_information
  217. assert 'task_name' in task_information
  218. # we use must_exist info for database look-up optimization
  219. task_object, created = self._cached_get_or_create(Task,
  220. build=task_information['build'],
  221. recipe=task_information['recipe'],
  222. task_name=task_information['task_name']
  223. )
  224. if created and must_exist:
  225. task_information['debug'] = "build id %d, recipe id %d" % (task_information['build'].pk, task_information['recipe'].pk)
  226. raise NotExisting("Task object created when expected to exist", task_information)
  227. object_changed = False
  228. for v in vars(task_object):
  229. if v in task_information.keys():
  230. if vars(task_object)[v] != task_information[v]:
  231. vars(task_object)[v] = task_information[v]
  232. object_changed = True
  233. # update setscene-related information if the task has a setscene
  234. if task_object.outcome == Task.OUTCOME_COVERED and 1 == task_object.get_related_setscene().count():
  235. task_object.outcome = Task.OUTCOME_CACHED
  236. object_changed = True
  237. outcome_task_setscene = Task.objects.get(task_executed=True, build = task_object.build,
  238. recipe = task_object.recipe, task_name=task_object.task_name+"_setscene").outcome
  239. if outcome_task_setscene == Task.OUTCOME_SUCCESS:
  240. task_object.sstate_result = Task.SSTATE_RESTORED
  241. object_changed = True
  242. elif outcome_task_setscene == Task.OUTCOME_FAILED:
  243. task_object.sstate_result = Task.SSTATE_FAILED
  244. object_changed = True
  245. if object_changed:
  246. task_object.save()
  247. return task_object
  248. def get_update_recipe_object(self, recipe_information, must_exist = False):
  249. assert 'layer_version' in recipe_information
  250. assert 'file_path' in recipe_information
  251. assert 'pathflags' in recipe_information
  252. assert not recipe_information['file_path'].startswith("/") # we should have layer-relative paths at all times
  253. def update_recipe_obj(recipe_object):
  254. object_changed = False
  255. for v in vars(recipe_object):
  256. if v in recipe_information.keys():
  257. object_changed = True
  258. vars(recipe_object)[v] = recipe_information[v]
  259. if object_changed:
  260. recipe_object.save()
  261. recipe, created = self._cached_get_or_create(Recipe, layer_version=recipe_information['layer_version'],
  262. file_path=recipe_information['file_path'], pathflags = recipe_information['pathflags'])
  263. update_recipe_obj(recipe)
  264. built_recipe = None
  265. # Create a copy of the recipe for historical puposes and update it
  266. for built_layer in self.layer_version_built:
  267. if built_layer.layer == recipe_information['layer_version'].layer:
  268. built_recipe, c = self._cached_get_or_create(Recipe,
  269. layer_version=built_layer,
  270. file_path=recipe_information['file_path'],
  271. pathflags = recipe_information['pathflags'])
  272. update_recipe_obj(built_recipe)
  273. break
  274. # If we're in analysis mode or if this is a custom recipe
  275. # then we are wholly responsible for the data
  276. # and therefore we return the 'real' recipe rather than the build
  277. # history copy of the recipe.
  278. if recipe_information['layer_version'].build is not None and \
  279. recipe_information['layer_version'].build.project == \
  280. Project.objects.get_or_create_default_project():
  281. return recipe
  282. if built_recipe is None:
  283. return recipe
  284. return built_recipe
  285. def get_update_layer_version_object(self, build_obj, layer_obj, layer_version_information):
  286. if isinstance(layer_obj, Layer_Version):
  287. # Special case the toaster-custom-images layer which is created
  288. # on the fly so don't update the values which may cause the layer
  289. # to be duplicated on a future get_or_create
  290. if layer_obj.layer.name == CustomImageRecipe.LAYER_NAME:
  291. return layer_obj
  292. # We already found our layer version for this build so just
  293. # update it with the new build information
  294. logger.debug("We found our layer from toaster")
  295. layer_obj.local_path = layer_version_information['local_path']
  296. layer_obj.save()
  297. self.layer_version_objects.append(layer_obj)
  298. # create a new copy of this layer version as a snapshot for
  299. # historical purposes
  300. layer_copy, c = Layer_Version.objects.get_or_create(
  301. build=build_obj,
  302. layer=layer_obj.layer,
  303. up_branch=layer_obj.up_branch,
  304. branch=layer_version_information['branch'],
  305. commit=layer_version_information['commit'],
  306. local_path=layer_version_information['local_path'],
  307. )
  308. logger.info("created new historical layer version %d",
  309. layer_copy.pk)
  310. self.layer_version_built.append(layer_copy)
  311. return layer_obj
  312. assert isinstance(build_obj, Build)
  313. assert isinstance(layer_obj, Layer)
  314. assert 'branch' in layer_version_information
  315. assert 'commit' in layer_version_information
  316. assert 'priority' in layer_version_information
  317. assert 'local_path' in layer_version_information
  318. # If we're doing a command line build then associate this new layer with the
  319. # project to avoid it 'contaminating' toaster data
  320. project = None
  321. if build_obj.project == Project.objects.get_or_create_default_project():
  322. project = build_obj.project
  323. layer_version_object, _ = Layer_Version.objects.get_or_create(
  324. build = build_obj,
  325. layer = layer_obj,
  326. branch = layer_version_information['branch'],
  327. commit = layer_version_information['commit'],
  328. priority = layer_version_information['priority'],
  329. local_path = layer_version_information['local_path'],
  330. project=project)
  331. self.layer_version_objects.append(layer_version_object)
  332. return layer_version_object
  333. def get_update_layer_object(self, layer_information, brbe):
  334. assert 'name' in layer_information
  335. assert 'layer_index_url' in layer_information
  336. if brbe is None:
  337. layer_object, _ = Layer.objects.get_or_create(
  338. name=layer_information['name'],
  339. layer_index_url=layer_information['layer_index_url'])
  340. return layer_object
  341. else:
  342. # we are under managed mode; we must match the layer used in the Project Layer
  343. br_id, be_id = brbe.split(":")
  344. # find layer by checkout path;
  345. from bldcontrol import bbcontroller
  346. bc = bbcontroller.getBuildEnvironmentController(pk = be_id)
  347. # we might have a race condition here, as the project layers may change between the build trigger and the actual build execution
  348. # 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
  349. # note that this is different
  350. buildrequest = BuildRequest.objects.get(pk = br_id)
  351. for brl in buildrequest.brlayer_set.all():
  352. localdirname = os.path.join(bc.getGitCloneDirectory(brl.giturl, brl.commit), brl.dirpath)
  353. # we get a relative path, unless running in HEAD mode where the path is absolute
  354. if not localdirname.startswith("/"):
  355. localdirname = os.path.join(bc.be.sourcedir, localdirname)
  356. #logger.debug(1, "Localdirname %s lcal_path %s" % (localdirname, layer_information['local_path']))
  357. if localdirname.startswith(layer_information['local_path']):
  358. # If the build request came from toaster this field
  359. # should contain the information from the layer_version
  360. # That created this build request.
  361. if brl.layer_version:
  362. return brl.layer_version
  363. # we matched the BRLayer, but we need the layer_version that generated this BR; reverse of the Project.schedule_build()
  364. #logger.debug(1, "Matched %s to BRlayer %s" % (pformat(layer_information["local_path"]), localdirname))
  365. for pl in buildrequest.project.projectlayer_set.filter(layercommit__layer__name = brl.name):
  366. if pl.layercommit.layer.vcs_url == brl.giturl :
  367. layer = pl.layercommit.layer
  368. layer.save()
  369. return layer
  370. raise NotExisting("Unidentified layer %s" % pformat(layer_information))
  371. def save_target_file_information(self, build_obj, target_obj, filedata):
  372. assert isinstance(build_obj, Build)
  373. assert isinstance(target_obj, Target)
  374. dirs = filedata['dirs']
  375. files = filedata['files']
  376. syms = filedata['syms']
  377. # always create the root directory as a special case;
  378. # note that this is never displayed, so the owner, group,
  379. # size, permission are irrelevant
  380. tf_obj = Target_File.objects.create(target = target_obj,
  381. path = '/',
  382. size = 0,
  383. owner = '',
  384. group = '',
  385. permission = '',
  386. inodetype = Target_File.ITYPE_DIRECTORY)
  387. tf_obj.save()
  388. # insert directories, ordered by name depth
  389. for d in sorted(dirs, key=lambda x:len(x[-1].split("/"))):
  390. (user, group, size) = d[1:4]
  391. permission = d[0][1:]
  392. path = d[4].lstrip(".")
  393. # we already created the root directory, so ignore any
  394. # entry for it
  395. if len(path) == 0:
  396. continue
  397. parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1])
  398. if len(parent_path) == 0:
  399. parent_path = "/"
  400. parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY)
  401. tf_obj = Target_File.objects.create(
  402. target = target_obj,
  403. path = unicode(path, 'utf-8'),
  404. size = size,
  405. inodetype = Target_File.ITYPE_DIRECTORY,
  406. permission = permission,
  407. owner = user,
  408. group = group,
  409. directory = parent_obj)
  410. # we insert files
  411. for d in files:
  412. (user, group, size) = d[1:4]
  413. permission = d[0][1:]
  414. path = d[4].lstrip(".")
  415. parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1])
  416. inodetype = Target_File.ITYPE_REGULAR
  417. if d[0].startswith('b'):
  418. inodetype = Target_File.ITYPE_BLOCK
  419. if d[0].startswith('c'):
  420. inodetype = Target_File.ITYPE_CHARACTER
  421. if d[0].startswith('p'):
  422. inodetype = Target_File.ITYPE_FIFO
  423. tf_obj = Target_File.objects.create(
  424. target = target_obj,
  425. path = unicode(path, 'utf-8'),
  426. size = size,
  427. inodetype = inodetype,
  428. permission = permission,
  429. owner = user,
  430. group = group)
  431. parent_obj = self._cached_get(Target_File, target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY)
  432. tf_obj.directory = parent_obj
  433. tf_obj.save()
  434. # we insert symlinks
  435. for d in syms:
  436. (user, group, size) = d[1:4]
  437. permission = d[0][1:]
  438. path = d[4].lstrip(".")
  439. filetarget_path = d[6]
  440. parent_path = "/".join(path.split("/")[:len(path.split("/")) - 1])
  441. if not filetarget_path.startswith("/"):
  442. # we have a relative path, get a normalized absolute one
  443. filetarget_path = parent_path + "/" + filetarget_path
  444. fcp = filetarget_path.split("/")
  445. fcpl = []
  446. for i in fcp:
  447. if i == "..":
  448. fcpl.pop()
  449. else:
  450. fcpl.append(i)
  451. filetarget_path = "/".join(fcpl)
  452. try:
  453. filetarget_obj = Target_File.objects.get(
  454. target = target_obj,
  455. path = unicode(filetarget_path, 'utf-8'))
  456. except Target_File.DoesNotExist:
  457. # we might have an invalid link; no way to detect this. just set it to None
  458. filetarget_obj = None
  459. parent_obj = Target_File.objects.get(target = target_obj, path = parent_path, inodetype = Target_File.ITYPE_DIRECTORY)
  460. tf_obj = Target_File.objects.create(
  461. target = target_obj,
  462. path = unicode(path, 'utf-8'),
  463. size = size,
  464. inodetype = Target_File.ITYPE_SYMLINK,
  465. permission = permission,
  466. owner = user,
  467. group = group,
  468. directory = parent_obj,
  469. sym_target = filetarget_obj)
  470. def save_target_package_information(self, build_obj, target_obj, packagedict, pkgpnmap, recipes, built_package=False):
  471. assert isinstance(build_obj, Build)
  472. assert isinstance(target_obj, Target)
  473. errormsg = ""
  474. for p in packagedict:
  475. # Search name swtiches round the installed name vs package name
  476. # by default installed name == package name
  477. searchname = p
  478. if p not in pkgpnmap:
  479. logger.warning("Image packages list contains %p, but is"
  480. " missing from all packages list where the"
  481. " metadata comes from. Skipping...", p)
  482. continue
  483. if 'OPKGN' in pkgpnmap[p].keys():
  484. searchname = pkgpnmap[p]['OPKGN']
  485. built_recipe = recipes[pkgpnmap[p]['PN']]
  486. if built_package:
  487. packagedict[p]['object'], created = Package.objects.get_or_create( build = build_obj, name = searchname )
  488. recipe = built_recipe
  489. else:
  490. packagedict[p]['object'], created = \
  491. CustomImagePackage.objects.get_or_create(name=searchname)
  492. # Clear the Package_Dependency objects as we're going to update
  493. # the CustomImagePackage with the latest dependency information
  494. packagedict[p]['object'].package_dependencies_target.all().delete()
  495. packagedict[p]['object'].package_dependencies_source.all().delete()
  496. try:
  497. recipe = self._cached_get(
  498. Recipe,
  499. name=built_recipe.name,
  500. layer_version__build=None,
  501. layer_version__up_branch=
  502. built_recipe.layer_version.up_branch,
  503. file_path=built_recipe.file_path,
  504. version=built_recipe.version
  505. )
  506. except (Recipe.DoesNotExist,
  507. Recipe.MultipleObjectsReturned) as e:
  508. logger.info("We did not find one recipe for the"
  509. "configuration data package %s %s" % (p, e))
  510. continue
  511. 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]
  512. # fill in everything we can from the runtime-reverse package data
  513. try:
  514. packagedict[p]['object'].recipe = recipe
  515. packagedict[p]['object'].version = pkgpnmap[p]['PV']
  516. packagedict[p]['object'].installed_name = p
  517. packagedict[p]['object'].revision = pkgpnmap[p]['PR']
  518. packagedict[p]['object'].license = pkgpnmap[p]['LICENSE']
  519. packagedict[p]['object'].section = pkgpnmap[p]['SECTION']
  520. packagedict[p]['object'].summary = pkgpnmap[p]['SUMMARY']
  521. packagedict[p]['object'].description = pkgpnmap[p]['DESCRIPTION']
  522. packagedict[p]['object'].size = int(pkgpnmap[p]['PKGSIZE'])
  523. # no files recorded for this package, so save files info
  524. packagefile_objects = []
  525. for targetpath in pkgpnmap[p]['FILES_INFO']:
  526. targetfilesize = pkgpnmap[p]['FILES_INFO'][targetpath]
  527. packagefile_objects.append(Package_File( package = packagedict[p]['object'],
  528. path = targetpath,
  529. size = targetfilesize))
  530. if len(packagefile_objects):
  531. Package_File.objects.bulk_create(packagefile_objects)
  532. except KeyError as e:
  533. errormsg += " stpi: Key error, package %s key %s \n" % ( p, e )
  534. # save disk installed size
  535. packagedict[p]['object'].installed_size = packagedict[p]['size']
  536. packagedict[p]['object'].save()
  537. if built_package:
  538. Target_Installed_Package.objects.create(target = target_obj, package = packagedict[p]['object'])
  539. packagedeps_objs = []
  540. for p in packagedict:
  541. for (px,deptype) in packagedict[p]['depends']:
  542. if deptype == 'depends':
  543. tdeptype = Package_Dependency.TYPE_TRDEPENDS
  544. elif deptype == 'recommends':
  545. tdeptype = Package_Dependency.TYPE_TRECOMMENDS
  546. try:
  547. packagedeps_objs.append(Package_Dependency(
  548. package = packagedict[p]['object'],
  549. depends_on = packagedict[px]['object'],
  550. dep_type = tdeptype,
  551. target = target_obj))
  552. except KeyError as e:
  553. logger.warning("Could not add dependency to the package %s "
  554. "because %s is an unknown package", p, px)
  555. if len(packagedeps_objs) > 0:
  556. Package_Dependency.objects.bulk_create(packagedeps_objs)
  557. else:
  558. logger.info("No package dependencies created")
  559. if len(errormsg) > 0:
  560. logger.warning("buildinfohelper: target_package_info could not identify recipes: \n%s", errormsg)
  561. def save_target_image_file_information(self, target_obj, file_name, file_size):
  562. Target_Image_File.objects.create( target = target_obj,
  563. file_name = file_name,
  564. file_size = file_size)
  565. def save_artifact_information(self, build_obj, file_name, file_size):
  566. # we skip the image files from other builds
  567. if Target_Image_File.objects.filter(file_name = file_name).count() > 0:
  568. return
  569. # do not update artifacts found in other builds
  570. if BuildArtifact.objects.filter(file_name = file_name).count() > 0:
  571. return
  572. BuildArtifact.objects.create(build = build_obj, file_name = file_name, file_size = file_size)
  573. def create_logmessage(self, log_information):
  574. assert 'build' in log_information
  575. assert 'level' in log_information
  576. assert 'message' in log_information
  577. log_object = LogMessage.objects.create(
  578. build = log_information['build'],
  579. level = log_information['level'],
  580. message = log_information['message'])
  581. for v in vars(log_object):
  582. if v in log_information.keys():
  583. vars(log_object)[v] = log_information[v]
  584. return log_object.save()
  585. def save_build_package_information(self, build_obj, package_info, recipes,
  586. built_package):
  587. # assert isinstance(build_obj, Build)
  588. # create and save the object
  589. pname = package_info['PKG']
  590. built_recipe = recipes[package_info['PN']]
  591. if 'OPKGN' in package_info.keys():
  592. pname = package_info['OPKGN']
  593. if built_package:
  594. bp_object, _ = Package.objects.get_or_create( build = build_obj,
  595. name = pname )
  596. recipe = built_recipe
  597. else:
  598. bp_object, created = \
  599. CustomImagePackage.objects.get_or_create(name=pname)
  600. try:
  601. recipe = self._cached_get(Recipe,
  602. name=built_recipe.name,
  603. layer_version__build=None,
  604. file_path=built_recipe.file_path,
  605. version=built_recipe.version)
  606. except (Recipe.DoesNotExist, Recipe.MultipleObjectsReturned):
  607. logger.debug("We did not find one recipe for the configuration"
  608. "data package %s" % pname)
  609. return
  610. bp_object.installed_name = package_info['PKG']
  611. bp_object.recipe = recipe
  612. bp_object.version = package_info['PKGV']
  613. bp_object.revision = package_info['PKGR']
  614. bp_object.summary = package_info['SUMMARY']
  615. bp_object.description = package_info['DESCRIPTION']
  616. bp_object.size = int(package_info['PKGSIZE'])
  617. bp_object.section = package_info['SECTION']
  618. bp_object.license = package_info['LICENSE']
  619. bp_object.save()
  620. # save any attached file information
  621. packagefile_objects = []
  622. for path in package_info['FILES_INFO']:
  623. packagefile_objects.append(Package_File( package = bp_object,
  624. path = path,
  625. size = package_info['FILES_INFO'][path] ))
  626. if len(packagefile_objects):
  627. Package_File.objects.bulk_create(packagefile_objects)
  628. def _po_byname(p):
  629. if built_package:
  630. pkg, created = Package.objects.get_or_create(build=build_obj,
  631. name=p)
  632. else:
  633. pkg, created = CustomImagePackage.objects.get_or_create(name=p)
  634. if created:
  635. pkg.size = -1
  636. pkg.save()
  637. return pkg
  638. packagedeps_objs = []
  639. # save soft dependency information
  640. if 'RDEPENDS' in package_info and package_info['RDEPENDS']:
  641. for p in bb.utils.explode_deps(package_info['RDEPENDS']):
  642. packagedeps_objs.append(Package_Dependency( package = bp_object,
  643. depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RDEPENDS))
  644. if 'RPROVIDES' in package_info and package_info['RPROVIDES']:
  645. for p in bb.utils.explode_deps(package_info['RPROVIDES']):
  646. packagedeps_objs.append(Package_Dependency( package = bp_object,
  647. depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RPROVIDES))
  648. if 'RRECOMMENDS' in package_info and package_info['RRECOMMENDS']:
  649. for p in bb.utils.explode_deps(package_info['RRECOMMENDS']):
  650. packagedeps_objs.append(Package_Dependency( package = bp_object,
  651. depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RRECOMMENDS))
  652. if 'RSUGGESTS' in package_info and package_info['RSUGGESTS']:
  653. for p in bb.utils.explode_deps(package_info['RSUGGESTS']):
  654. packagedeps_objs.append(Package_Dependency( package = bp_object,
  655. depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RSUGGESTS))
  656. if 'RREPLACES' in package_info and package_info['RREPLACES']:
  657. for p in bb.utils.explode_deps(package_info['RREPLACES']):
  658. packagedeps_objs.append(Package_Dependency( package = bp_object,
  659. depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RREPLACES))
  660. if 'RCONFLICTS' in package_info and package_info['RCONFLICTS']:
  661. for p in bb.utils.explode_deps(package_info['RCONFLICTS']):
  662. packagedeps_objs.append(Package_Dependency( package = bp_object,
  663. depends_on = _po_byname(p), dep_type = Package_Dependency.TYPE_RCONFLICTS))
  664. if len(packagedeps_objs) > 0:
  665. Package_Dependency.objects.bulk_create(packagedeps_objs)
  666. return bp_object
  667. def save_build_variables(self, build_obj, vardump):
  668. assert isinstance(build_obj, Build)
  669. for k in vardump:
  670. desc = vardump[k]['doc']
  671. if desc is None:
  672. var_words = [word for word in k.split('_')]
  673. root_var = "_".join([word for word in var_words if word.isupper()])
  674. if root_var and root_var != k and root_var in vardump:
  675. desc = vardump[root_var]['doc']
  676. if desc is None:
  677. desc = ''
  678. if len(desc):
  679. HelpText.objects.get_or_create(build=build_obj,
  680. area=HelpText.VARIABLE,
  681. key=k, text=desc)
  682. if not bool(vardump[k]['func']):
  683. value = vardump[k]['v']
  684. if value is None:
  685. value = ''
  686. variable_obj = Variable.objects.create( build = build_obj,
  687. variable_name = k,
  688. variable_value = value,
  689. description = desc)
  690. varhist_objects = []
  691. for vh in vardump[k]['history']:
  692. if not 'documentation.conf' in vh['file']:
  693. varhist_objects.append(VariableHistory( variable = variable_obj,
  694. file_name = vh['file'],
  695. line_number = vh['line'],
  696. operation = vh['op']))
  697. if len(varhist_objects):
  698. VariableHistory.objects.bulk_create(varhist_objects)
  699. class MockEvent(object):
  700. """ This object is used to create event, for which normal event-processing methods can
  701. be used, out of data that is not coming via an actual event
  702. """
  703. def __init__(self):
  704. self.msg = None
  705. self.levelno = None
  706. self.taskname = None
  707. self.taskhash = None
  708. self.pathname = None
  709. self.lineno = None
  710. class BuildInfoHelper(object):
  711. """ This class gathers the build information from the server and sends it
  712. towards the ORM wrapper for storing in the database
  713. It is instantiated once per build
  714. Keeps in memory all data that needs matching before writing it to the database
  715. """
  716. # pylint: disable=protected-access
  717. # the code will look into the protected variables of the event; no easy way around this
  718. # pylint: disable=bad-continuation
  719. # we do not follow the python conventions for continuation indentation due to long lines here
  720. def __init__(self, server, has_build_history = False, brbe = None):
  721. self.internal_state = {}
  722. self.internal_state['taskdata'] = {}
  723. self.internal_state['targets'] = []
  724. self.task_order = 0
  725. self.autocommit_step = 1
  726. self.server = server
  727. # we use manual transactions if the database doesn't autocommit on us
  728. if not connection.features.autocommits_when_autocommit_is_off:
  729. transaction.set_autocommit(False)
  730. self.orm_wrapper = ORMWrapper()
  731. self.has_build_history = has_build_history
  732. self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0]
  733. # this is set for Toaster-triggered builds by localhostbecontroller
  734. # via toasterui
  735. self.brbe = brbe
  736. self.project = None
  737. logger.debug(1, "buildinfohelper: Build info helper inited %s" % vars(self))
  738. ###################
  739. ## methods to convert event/external info into objects that the ORM layer uses
  740. def _get_build_information(self, build_log_path):
  741. build_info = {}
  742. build_info['machine'] = self.server.runCommand(["getVariable", "MACHINE"])[0]
  743. build_info['distro'] = self.server.runCommand(["getVariable", "DISTRO"])[0]
  744. build_info['distro_version'] = self.server.runCommand(["getVariable", "DISTRO_VERSION"])[0]
  745. build_info['started_on'] = timezone.now()
  746. build_info['completed_on'] = timezone.now()
  747. build_info['cooker_log_path'] = build_log_path
  748. build_info['build_name'] = self.server.runCommand(["getVariable", "BUILDNAME"])[0]
  749. build_info['bitbake_version'] = self.server.runCommand(["getVariable", "BB_VERSION"])[0]
  750. build_info['project'] = self.project = self.server.runCommand(["getVariable", "TOASTER_PROJECT"])[0]
  751. return build_info
  752. def _get_task_information(self, event, recipe):
  753. assert 'taskname' in vars(event)
  754. task_information = {}
  755. task_information['build'] = self.internal_state['build']
  756. task_information['outcome'] = Task.OUTCOME_NA
  757. task_information['recipe'] = recipe
  758. task_information['task_name'] = event.taskname
  759. try:
  760. # some tasks don't come with a hash. and that's ok
  761. task_information['sstate_checksum'] = event.taskhash
  762. except AttributeError:
  763. pass
  764. return task_information
  765. def _get_layer_version_for_path(self, path):
  766. assert path.startswith("/")
  767. assert 'build' in self.internal_state
  768. def _slkey_interactive(layer_version):
  769. assert isinstance(layer_version, Layer_Version)
  770. return len(layer_version.local_path)
  771. # Heuristics: we always match recipe to the deepest layer path in the discovered layers
  772. for lvo in sorted(self.orm_wrapper.layer_version_objects, reverse=True, key=_slkey_interactive):
  773. # we can match to the recipe file path
  774. if path.startswith(lvo.local_path):
  775. return lvo
  776. #if we get here, we didn't read layers correctly; dump whatever information we have on the error log
  777. logger.warning("Could not match layer version for recipe path %s : %s", path, self.orm_wrapper.layer_version_objects)
  778. #mockup the new layer
  779. unknown_layer, _ = Layer.objects.get_or_create(name="Unidentified layer", layer_index_url="")
  780. unknown_layer_version_obj, _ = Layer_Version.objects.get_or_create(layer = unknown_layer, build = self.internal_state['build'])
  781. # append it so we don't run into this error again and again
  782. self.orm_wrapper.layer_version_objects.append(unknown_layer_version_obj)
  783. return unknown_layer_version_obj
  784. def _get_recipe_information_from_taskfile(self, taskfile):
  785. localfilepath = taskfile.split(":")[-1]
  786. filepath_flags = ":".join(sorted(taskfile.split(":")[:-1]))
  787. layer_version_obj = self._get_layer_version_for_path(localfilepath)
  788. recipe_info = {}
  789. recipe_info['layer_version'] = layer_version_obj
  790. recipe_info['file_path'] = localfilepath
  791. recipe_info['pathflags'] = filepath_flags
  792. if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path):
  793. recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/")
  794. else:
  795. raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path))
  796. return recipe_info
  797. def _get_path_information(self, task_object):
  798. assert isinstance(task_object, Task)
  799. build_stats_format = "{tmpdir}/buildstats/{buildname}/{package}/"
  800. build_stats_path = []
  801. for t in self.internal_state['targets']:
  802. buildname = self.internal_state['build'].build_name
  803. pe, pv = task_object.recipe.version.split(":",1)
  804. if len(pe) > 0:
  805. package = task_object.recipe.name + "-" + pe + "_" + pv
  806. else:
  807. package = task_object.recipe.name + "-" + pv
  808. build_stats_path.append(build_stats_format.format(tmpdir=self.tmp_dir,
  809. buildname=buildname,
  810. package=package))
  811. return build_stats_path
  812. ################################
  813. ## external available methods to store information
  814. @staticmethod
  815. def _get_data_from_event(event):
  816. evdata = None
  817. if '_localdata' in vars(event):
  818. evdata = event._localdata
  819. elif 'data' in vars(event):
  820. evdata = event.data
  821. else:
  822. raise Exception("Event with neither _localdata or data properties")
  823. return evdata
  824. def store_layer_info(self, event):
  825. layerinfos = BuildInfoHelper._get_data_from_event(event)
  826. self.internal_state['lvs'] = {}
  827. for layer in layerinfos:
  828. try:
  829. self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)] = layerinfos[layer]['version']
  830. self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)]['local_path'] = layerinfos[layer]['local_path']
  831. except NotExisting as nee:
  832. logger.warning("buildinfohelper: cannot identify layer exception:%s ", nee)
  833. def store_started_build(self, event, build_log_path):
  834. assert '_pkgs' in vars(event)
  835. build_information = self._get_build_information(build_log_path)
  836. # Update brbe and project as they can be changed for every build
  837. self.project = build_information['project']
  838. build_obj = self.orm_wrapper.create_build_object(build_information, self.brbe, self.project)
  839. self.internal_state['build'] = build_obj
  840. # save layer version information for this build
  841. if not 'lvs' in self.internal_state:
  842. logger.error("Layer version information not found; Check if the bitbake server was configured to inherit toaster.bbclass.")
  843. else:
  844. for layer_obj in self.internal_state['lvs']:
  845. self.orm_wrapper.get_update_layer_version_object(build_obj, layer_obj, self.internal_state['lvs'][layer_obj])
  846. del self.internal_state['lvs']
  847. # create target information
  848. target_information = {}
  849. target_information['targets'] = event._pkgs
  850. target_information['build'] = build_obj
  851. self.internal_state['targets'] = self.orm_wrapper.get_or_create_targets(target_information)
  852. # Save build configuration
  853. data = self.server.runCommand(["getAllKeysWithFlags", ["doc", "func"]])[0]
  854. # convert the paths from absolute to relative to either the build directory or layer checkouts
  855. path_prefixes = []
  856. if self.brbe is not None:
  857. _, be_id = self.brbe.split(":")
  858. be = BuildEnvironment.objects.get(pk = be_id)
  859. path_prefixes.append(be.builddir)
  860. for layer in sorted(self.orm_wrapper.layer_version_objects, key = lambda x:len(x.local_path), reverse=True):
  861. path_prefixes.append(layer.local_path)
  862. # we strip the prefixes
  863. for k in data:
  864. if not bool(data[k]['func']):
  865. for vh in data[k]['history']:
  866. if not 'documentation.conf' in vh['file']:
  867. abs_file_name = vh['file']
  868. for pp in path_prefixes:
  869. if abs_file_name.startswith(pp + "/"):
  870. vh['file']=abs_file_name[len(pp + "/"):]
  871. break
  872. # save the variables
  873. self.orm_wrapper.save_build_variables(build_obj, data)
  874. return self.brbe
  875. def update_target_image_file(self, event):
  876. evdata = BuildInfoHelper._get_data_from_event(event)
  877. for t in self.internal_state['targets']:
  878. if t.is_image == True:
  879. output_files = list(evdata.viewkeys())
  880. for output in output_files:
  881. if t.target in output and 'rootfs' in output and not output.endswith(".manifest"):
  882. self.orm_wrapper.save_target_image_file_information(t, output, evdata[output])
  883. def update_artifact_image_file(self, event):
  884. evdata = BuildInfoHelper._get_data_from_event(event)
  885. for artifact_path in evdata.keys():
  886. self.orm_wrapper.save_artifact_information(self.internal_state['build'], artifact_path, evdata[artifact_path])
  887. def update_build_information(self, event, errors, warnings, taskfailures):
  888. if 'build' in self.internal_state:
  889. self.orm_wrapper.update_build_object(self.internal_state['build'], errors, warnings, taskfailures)
  890. def store_license_manifest_path(self, event):
  891. deploy_dir = BuildInfoHelper._get_data_from_event(event)['deploy_dir']
  892. image_name = BuildInfoHelper._get_data_from_event(event)['image_name']
  893. path = deploy_dir + "/licenses/" + image_name + "/license.manifest"
  894. for target in self.internal_state['targets']:
  895. if target.target in image_name:
  896. self.orm_wrapper.update_target_set_license_manifest(target, path)
  897. def store_started_task(self, event):
  898. assert isinstance(event, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted, bb.runqueue.runQueueTaskSkipped))
  899. assert 'taskfile' in vars(event)
  900. localfilepath = event.taskfile.split(":")[-1]
  901. assert localfilepath.startswith("/")
  902. identifier = event.taskfile + ":" + event.taskname
  903. recipe_information = self._get_recipe_information_from_taskfile(event.taskfile)
  904. recipe = self.orm_wrapper.get_update_recipe_object(recipe_information, True)
  905. task_information = self._get_task_information(event, recipe)
  906. task_information['outcome'] = Task.OUTCOME_NA
  907. if isinstance(event, bb.runqueue.runQueueTaskSkipped):
  908. assert 'reason' in vars(event)
  909. task_information['task_executed'] = False
  910. if event.reason == "covered":
  911. task_information['outcome'] = Task.OUTCOME_COVERED
  912. if event.reason == "existing":
  913. task_information['outcome'] = Task.OUTCOME_PREBUILT
  914. else:
  915. task_information['task_executed'] = True
  916. if 'noexec' in vars(event) and event.noexec == True:
  917. task_information['task_executed'] = False
  918. task_information['outcome'] = Task.OUTCOME_EMPTY
  919. task_information['script_type'] = Task.CODING_NA
  920. # do not assign order numbers to scene tasks
  921. if not isinstance(event, bb.runqueue.sceneQueueTaskStarted):
  922. self.task_order += 1
  923. task_information['order'] = self.task_order
  924. self.orm_wrapper.get_update_task_object(task_information)
  925. self.internal_state['taskdata'][identifier] = {
  926. 'outcome': task_information['outcome'],
  927. }
  928. def store_tasks_stats(self, event):
  929. task_data = BuildInfoHelper._get_data_from_event(event)
  930. for (task_file, task_name, task_stats, recipe_name) in task_data:
  931. build = self.internal_state['build']
  932. self.orm_wrapper.update_task_object(build, task_name, recipe_name, task_stats)
  933. def update_and_store_task(self, event):
  934. assert 'taskfile' in vars(event)
  935. localfilepath = event.taskfile.split(":")[-1]
  936. assert localfilepath.startswith("/")
  937. identifier = event.taskfile + ":" + event.taskname
  938. if not identifier in self.internal_state['taskdata']:
  939. if isinstance(event, bb.build.TaskBase):
  940. # we do a bit of guessing
  941. candidates = [x for x in self.internal_state['taskdata'].keys() if x.endswith(identifier)]
  942. if len(candidates) == 1:
  943. identifier = candidates[0]
  944. assert identifier in self.internal_state['taskdata']
  945. identifierlist = identifier.split(":")
  946. realtaskfile = ":".join(identifierlist[0:len(identifierlist)-1])
  947. recipe_information = self._get_recipe_information_from_taskfile(realtaskfile)
  948. recipe = self.orm_wrapper.get_update_recipe_object(recipe_information, True)
  949. task_information = self._get_task_information(event,recipe)
  950. task_information['outcome'] = self.internal_state['taskdata'][identifier]['outcome']
  951. if 'logfile' in vars(event):
  952. task_information['logfile'] = event.logfile
  953. if '_message' in vars(event):
  954. task_information['message'] = event._message
  955. if 'taskflags' in vars(event):
  956. # with TaskStarted, we get even more information
  957. if 'python' in event.taskflags.keys() and event.taskflags['python'] == '1':
  958. task_information['script_type'] = Task.CODING_PYTHON
  959. else:
  960. task_information['script_type'] = Task.CODING_SHELL
  961. if task_information['outcome'] == Task.OUTCOME_NA:
  962. if isinstance(event, (bb.runqueue.runQueueTaskCompleted, bb.runqueue.sceneQueueTaskCompleted)):
  963. task_information['outcome'] = Task.OUTCOME_SUCCESS
  964. del self.internal_state['taskdata'][identifier]
  965. if isinstance(event, (bb.runqueue.runQueueTaskFailed, bb.runqueue.sceneQueueTaskFailed)):
  966. task_information['outcome'] = Task.OUTCOME_FAILED
  967. del self.internal_state['taskdata'][identifier]
  968. if not connection.features.autocommits_when_autocommit_is_off:
  969. # we force a sync point here, to get the progress bar to show
  970. if self.autocommit_step % 3 == 0:
  971. transaction.set_autocommit(True)
  972. transaction.set_autocommit(False)
  973. self.autocommit_step += 1
  974. self.orm_wrapper.get_update_task_object(task_information, True) # must exist
  975. def store_missed_state_tasks(self, event):
  976. for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['missed']:
  977. # identifier = fn + taskname + "_setscene"
  978. recipe_information = self._get_recipe_information_from_taskfile(fn)
  979. recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
  980. mevent = MockEvent()
  981. mevent.taskname = taskname
  982. mevent.taskhash = taskhash
  983. task_information = self._get_task_information(mevent,recipe)
  984. task_information['start_time'] = timezone.now()
  985. task_information['outcome'] = Task.OUTCOME_NA
  986. task_information['sstate_checksum'] = taskhash
  987. task_information['sstate_result'] = Task.SSTATE_MISS
  988. task_information['path_to_sstate_obj'] = sstatefile
  989. self.orm_wrapper.get_update_task_object(task_information)
  990. for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['found']:
  991. # identifier = fn + taskname + "_setscene"
  992. recipe_information = self._get_recipe_information_from_taskfile(fn)
  993. recipe = self.orm_wrapper.get_update_recipe_object(recipe_information)
  994. mevent = MockEvent()
  995. mevent.taskname = taskname
  996. mevent.taskhash = taskhash
  997. task_information = self._get_task_information(mevent,recipe)
  998. task_information['path_to_sstate_obj'] = sstatefile
  999. self.orm_wrapper.get_update_task_object(task_information)
  1000. def store_target_package_data(self, event):
  1001. # for all image targets
  1002. for target in self.internal_state['targets']:
  1003. if target.is_image:
  1004. pkgdata = BuildInfoHelper._get_data_from_event(event)['pkgdata']
  1005. imgdata = BuildInfoHelper._get_data_from_event(event)['imgdata'].get(target.target, {})
  1006. filedata = BuildInfoHelper._get_data_from_event(event)['filedata'].get(target.target, {})
  1007. try:
  1008. self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata, pkgdata, self.internal_state['recipes'], built_package=True)
  1009. self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata.copy(), pkgdata, self.internal_state['recipes'], built_package=False)
  1010. except KeyError as e:
  1011. logger.warning("KeyError in save_target_package_information"
  1012. "%s ", e)
  1013. try:
  1014. self.orm_wrapper.save_target_file_information(self.internal_state['build'], target, filedata)
  1015. except KeyError as e:
  1016. logger.warning("KeyError in save_target_file_information"
  1017. "%s ", e)
  1018. def store_dependency_information(self, event):
  1019. assert '_depgraph' in vars(event)
  1020. assert 'layer-priorities' in event._depgraph
  1021. assert 'pn' in event._depgraph
  1022. assert 'tdepends' in event._depgraph
  1023. errormsg = ""
  1024. # save layer version priorities
  1025. if 'layer-priorities' in event._depgraph.keys():
  1026. for lv in event._depgraph['layer-priorities']:
  1027. (_, path, _, priority) = lv
  1028. layer_version_obj = self._get_layer_version_for_path(path[1:]) # paths start with a ^
  1029. assert layer_version_obj is not None
  1030. layer_version_obj.priority = priority
  1031. layer_version_obj.save()
  1032. # save recipe information
  1033. self.internal_state['recipes'] = {}
  1034. for pn in event._depgraph['pn']:
  1035. file_name = event._depgraph['pn'][pn]['filename'].split(":")[-1]
  1036. pathflags = ":".join(sorted(event._depgraph['pn'][pn]['filename'].split(":")[:-1]))
  1037. layer_version_obj = self._get_layer_version_for_path(file_name)
  1038. assert layer_version_obj is not None
  1039. recipe_info = {}
  1040. recipe_info['name'] = pn
  1041. recipe_info['layer_version'] = layer_version_obj
  1042. if 'version' in event._depgraph['pn'][pn]:
  1043. recipe_info['version'] = event._depgraph['pn'][pn]['version'].lstrip(":")
  1044. if 'summary' in event._depgraph['pn'][pn]:
  1045. recipe_info['summary'] = event._depgraph['pn'][pn]['summary']
  1046. if 'license' in event._depgraph['pn'][pn]:
  1047. recipe_info['license'] = event._depgraph['pn'][pn]['license']
  1048. if 'description' in event._depgraph['pn'][pn]:
  1049. recipe_info['description'] = event._depgraph['pn'][pn]['description']
  1050. if 'section' in event._depgraph['pn'][pn]:
  1051. recipe_info['section'] = event._depgraph['pn'][pn]['section']
  1052. if 'homepage' in event._depgraph['pn'][pn]:
  1053. recipe_info['homepage'] = event._depgraph['pn'][pn]['homepage']
  1054. if 'bugtracker' in event._depgraph['pn'][pn]:
  1055. recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker']
  1056. recipe_info['file_path'] = file_name
  1057. recipe_info['pathflags'] = pathflags
  1058. if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path):
  1059. recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/")
  1060. else:
  1061. raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path))
  1062. recipe = self.orm_wrapper.get_update_recipe_object(recipe_info)
  1063. recipe.is_image = False
  1064. if 'inherits' in event._depgraph['pn'][pn].keys():
  1065. for cls in event._depgraph['pn'][pn]['inherits']:
  1066. if cls.endswith('/image.bbclass'):
  1067. recipe.is_image = True
  1068. recipe_info['is_image'] = True
  1069. # Save the is_image state to the relevant recipe objects
  1070. self.orm_wrapper.get_update_recipe_object(recipe_info)
  1071. break
  1072. if recipe.is_image:
  1073. for t in self.internal_state['targets']:
  1074. if pn == t.target:
  1075. t.is_image = True
  1076. t.save()
  1077. self.internal_state['recipes'][pn] = recipe
  1078. # we'll not get recipes for key w/ values listed in ASSUME_PROVIDED
  1079. assume_provided = self.server.runCommand(["getVariable", "ASSUME_PROVIDED"])[0].split()
  1080. # save recipe dependency
  1081. # buildtime
  1082. recipedeps_objects = []
  1083. for recipe in event._depgraph['depends']:
  1084. target = self.internal_state['recipes'][recipe]
  1085. for dep in event._depgraph['depends'][recipe]:
  1086. if dep in assume_provided:
  1087. continue
  1088. via = None
  1089. if 'providermap' in event._depgraph and dep in event._depgraph['providermap']:
  1090. deprecipe = event._depgraph['providermap'][dep][0]
  1091. dependency = self.internal_state['recipes'][deprecipe]
  1092. via = Provides.objects.get_or_create(name=dep,
  1093. recipe=dependency)[0]
  1094. elif dep in self.internal_state['recipes']:
  1095. dependency = self.internal_state['recipes'][dep]
  1096. else:
  1097. errormsg += " stpd: KeyError saving recipe dependency for %s, %s \n" % (recipe, dep)
  1098. continue
  1099. recipe_dep = Recipe_Dependency(recipe=target,
  1100. depends_on=dependency,
  1101. via=via,
  1102. dep_type=Recipe_Dependency.TYPE_DEPENDS)
  1103. recipedeps_objects.append(recipe_dep)
  1104. Recipe_Dependency.objects.bulk_create(recipedeps_objects)
  1105. # save all task information
  1106. def _save_a_task(taskdesc):
  1107. spec = re.split(r'\.', taskdesc)
  1108. pn = ".".join(spec[0:-1])
  1109. taskname = spec[-1]
  1110. e = event
  1111. e.taskname = pn
  1112. recipe = self.internal_state['recipes'][pn]
  1113. task_info = self._get_task_information(e, recipe)
  1114. task_info['task_name'] = taskname
  1115. task_obj = self.orm_wrapper.get_update_task_object(task_info)
  1116. return task_obj
  1117. # create tasks
  1118. tasks = {}
  1119. for taskdesc in event._depgraph['tdepends']:
  1120. tasks[taskdesc] = _save_a_task(taskdesc)
  1121. # create dependencies between tasks
  1122. taskdeps_objects = []
  1123. for taskdesc in event._depgraph['tdepends']:
  1124. target = tasks[taskdesc]
  1125. for taskdep in event._depgraph['tdepends'][taskdesc]:
  1126. if taskdep not in tasks:
  1127. # Fetch tasks info is not collected previously
  1128. dep = _save_a_task(taskdep)
  1129. else:
  1130. dep = tasks[taskdep]
  1131. taskdeps_objects.append(Task_Dependency( task = target, depends_on = dep ))
  1132. Task_Dependency.objects.bulk_create(taskdeps_objects)
  1133. if len(errormsg) > 0:
  1134. logger.warning("buildinfohelper: dependency info not identify recipes: \n%s", errormsg)
  1135. def store_build_package_information(self, event):
  1136. package_info = BuildInfoHelper._get_data_from_event(event)
  1137. self.orm_wrapper.save_build_package_information(
  1138. self.internal_state['build'],
  1139. package_info,
  1140. self.internal_state['recipes'],
  1141. built_package=True)
  1142. self.orm_wrapper.save_build_package_information(
  1143. self.internal_state['build'],
  1144. package_info,
  1145. self.internal_state['recipes'],
  1146. built_package=False)
  1147. def _store_build_done(self, errorcode):
  1148. logger.info("Build exited with errorcode %d", errorcode)
  1149. br_id, be_id = self.brbe.split(":")
  1150. be = BuildEnvironment.objects.get(pk = be_id)
  1151. be.lock = BuildEnvironment.LOCK_LOCK
  1152. be.save()
  1153. br = BuildRequest.objects.get(pk = br_id)
  1154. # if we're 'done' because we got cancelled update the build outcome
  1155. if br.state == BuildRequest.REQ_CANCELLING:
  1156. logger.info("Build cancelled")
  1157. br.build.outcome = Build.CANCELLED
  1158. br.build.save()
  1159. self.internal_state['build'] = br.build
  1160. errorcode = 0
  1161. if errorcode == 0:
  1162. # request archival of the project artifacts
  1163. br.state = BuildRequest.REQ_COMPLETED
  1164. else:
  1165. br.state = BuildRequest.REQ_FAILED
  1166. br.save()
  1167. def store_log_error(self, text):
  1168. mockevent = MockEvent()
  1169. mockevent.levelno = formatter.ERROR
  1170. mockevent.msg = text
  1171. mockevent.pathname = '-- None'
  1172. mockevent.lineno = LogMessage.ERROR
  1173. self.store_log_event(mockevent)
  1174. def store_log_exception(self, text, backtrace = ""):
  1175. mockevent = MockEvent()
  1176. mockevent.levelno = -1
  1177. mockevent.msg = text
  1178. mockevent.pathname = backtrace
  1179. mockevent.lineno = -1
  1180. self.store_log_event(mockevent)
  1181. def store_log_event(self, event):
  1182. if event.levelno < formatter.WARNING:
  1183. return
  1184. if 'args' in vars(event):
  1185. event.msg = event.msg % event.args
  1186. if not 'build' in self.internal_state:
  1187. if self.brbe is None:
  1188. if not 'backlog' in self.internal_state:
  1189. self.internal_state['backlog'] = []
  1190. self.internal_state['backlog'].append(event)
  1191. return
  1192. else: # we're under Toaster control, the build is already created
  1193. br, _ = self.brbe.split(":")
  1194. buildrequest = BuildRequest.objects.get(pk = br)
  1195. self.internal_state['build'] = buildrequest.build
  1196. if 'build' in self.internal_state and 'backlog' in self.internal_state:
  1197. # if we have a backlog of events, do our best to save them here
  1198. if len(self.internal_state['backlog']):
  1199. tempevent = self.internal_state['backlog'].pop()
  1200. logger.debug(1, "buildinfohelper: Saving stored event %s " % tempevent)
  1201. self.store_log_event(tempevent)
  1202. else:
  1203. logger.info("buildinfohelper: All events saved")
  1204. del self.internal_state['backlog']
  1205. log_information = {}
  1206. log_information['build'] = self.internal_state['build']
  1207. if event.levelno == formatter.CRITICAL:
  1208. log_information['level'] = LogMessage.CRITICAL
  1209. elif event.levelno == formatter.ERROR:
  1210. log_information['level'] = LogMessage.ERROR
  1211. elif event.levelno == formatter.WARNING:
  1212. log_information['level'] = LogMessage.WARNING
  1213. elif event.levelno == -2: # toaster self-logging
  1214. log_information['level'] = -2
  1215. else:
  1216. log_information['level'] = LogMessage.INFO
  1217. log_information['message'] = event.msg
  1218. log_information['pathname'] = event.pathname
  1219. log_information['lineno'] = event.lineno
  1220. logger.info("Logging error 2: %s", log_information)
  1221. self.orm_wrapper.create_logmessage(log_information)
  1222. def close(self, errorcode):
  1223. if self.brbe is not None:
  1224. self._store_build_done(errorcode)
  1225. if 'backlog' in self.internal_state:
  1226. if 'build' in self.internal_state:
  1227. # we save missed events in the database for the current build
  1228. tempevent = self.internal_state['backlog'].pop()
  1229. self.store_log_event(tempevent)
  1230. else:
  1231. # we have no build, and we still have events; something amazingly wrong happend
  1232. for event in self.internal_state['backlog']:
  1233. logger.error("UNSAVED log: %s", event.msg)
  1234. if not connection.features.autocommits_when_autocommit_is_off:
  1235. transaction.set_autocommit(True)
  1236. # unset the brbe; this is to prevent subsequent command-line builds
  1237. # being incorrectly attached to the previous Toaster-triggered build;
  1238. # see https://bugzilla.yoctoproject.org/show_bug.cgi?id=9021
  1239. self.brbe = None