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