xref: /OK3568_Linux_fs/yocto/poky/scripts/lib/checklayer/cases/bsp.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# Copyright (C) 2017 Intel Corporation
2*4882a593Smuzhiyun#
3*4882a593Smuzhiyun# SPDX-License-Identifier: MIT
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyun
6*4882a593Smuzhiyunimport unittest
7*4882a593Smuzhiyun
8*4882a593Smuzhiyunfrom checklayer import LayerType, get_signatures, check_command, get_depgraph
9*4882a593Smuzhiyunfrom checklayer.case import OECheckLayerTestCase
10*4882a593Smuzhiyun
11*4882a593Smuzhiyunclass BSPCheckLayer(OECheckLayerTestCase):
12*4882a593Smuzhiyun    @classmethod
13*4882a593Smuzhiyun    def setUpClass(self):
14*4882a593Smuzhiyun        if self.tc.layer['type'] not in (LayerType.BSP, LayerType.CORE):
15*4882a593Smuzhiyun            raise unittest.SkipTest("BSPCheckLayer: Layer %s isn't BSP one." %\
16*4882a593Smuzhiyun                self.tc.layer['name'])
17*4882a593Smuzhiyun
18*4882a593Smuzhiyun    def test_bsp_defines_machines(self):
19*4882a593Smuzhiyun        self.assertTrue(self.tc.layer['conf']['machines'],
20*4882a593Smuzhiyun                "Layer is BSP but doesn't defines machines.")
21*4882a593Smuzhiyun
22*4882a593Smuzhiyun    def test_bsp_no_set_machine(self):
23*4882a593Smuzhiyun        from oeqa.utils.commands import get_bb_var
24*4882a593Smuzhiyun
25*4882a593Smuzhiyun        machine = get_bb_var('MACHINE')
26*4882a593Smuzhiyun        self.assertEqual(self.td['bbvars']['MACHINE'], machine,
27*4882a593Smuzhiyun                msg="Layer %s modified machine %s -> %s" % \
28*4882a593Smuzhiyun                    (self.tc.layer['name'], self.td['bbvars']['MACHINE'], machine))
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun
31*4882a593Smuzhiyun    def test_machine_world(self):
32*4882a593Smuzhiyun        '''
33*4882a593Smuzhiyun        "bitbake world" is expected to work regardless which machine is selected.
34*4882a593Smuzhiyun        BSP layers sometimes break that by enabling a recipe for a certain machine
35*4882a593Smuzhiyun        without checking whether that recipe actually can be built in the current
36*4882a593Smuzhiyun        distro configuration (for example, OpenGL might not enabled).
37*4882a593Smuzhiyun
38*4882a593Smuzhiyun        This test iterates over all machines. It would be nicer to instantiate
39*4882a593Smuzhiyun        it once per machine. It merely checks for errors during parse
40*4882a593Smuzhiyun        time. It does not actually attempt to build anything.
41*4882a593Smuzhiyun        '''
42*4882a593Smuzhiyun
43*4882a593Smuzhiyun        if not self.td['machines']:
44*4882a593Smuzhiyun            self.skipTest('No machines set with --machines.')
45*4882a593Smuzhiyun        msg = []
46*4882a593Smuzhiyun        for machine in self.td['machines']:
47*4882a593Smuzhiyun            # In contrast to test_machine_signatures() below, errors are fatal here.
48*4882a593Smuzhiyun            try:
49*4882a593Smuzhiyun                get_signatures(self.td['builddir'], failsafe=False, machine=machine)
50*4882a593Smuzhiyun            except RuntimeError as ex:
51*4882a593Smuzhiyun                msg.append(str(ex))
52*4882a593Smuzhiyun        if msg:
53*4882a593Smuzhiyun            msg.insert(0, 'The following machines broke a world build:')
54*4882a593Smuzhiyun            self.fail('\n'.join(msg))
55*4882a593Smuzhiyun
56*4882a593Smuzhiyun    def test_machine_signatures(self):
57*4882a593Smuzhiyun        '''
58*4882a593Smuzhiyun        Selecting a machine may only affect the signature of tasks that are specific
59*4882a593Smuzhiyun        to that machine. In other words, when MACHINE=A and MACHINE=B share a recipe
60*4882a593Smuzhiyun        foo and the output of foo, then both machine configurations must build foo
61*4882a593Smuzhiyun        in exactly the same way. Otherwise it is not possible to use both machines
62*4882a593Smuzhiyun        in the same distribution.
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun        This criteria can only be tested by testing different machines in combination,
65*4882a593Smuzhiyun        i.e. one main layer, potentially several additional BSP layers and an explicit
66*4882a593Smuzhiyun        choice of machines:
67*4882a593Smuzhiyun        yocto-check-layer --additional-layers .../meta-intel --machines intel-corei7-64 imx6slevk -- .../meta-freescale
68*4882a593Smuzhiyun        '''
69*4882a593Smuzhiyun
70*4882a593Smuzhiyun        if not self.td['machines']:
71*4882a593Smuzhiyun            self.skipTest('No machines set with --machines.')
72*4882a593Smuzhiyun
73*4882a593Smuzhiyun        # Collect signatures for all machines that we are testing
74*4882a593Smuzhiyun        # and merge that into a hash:
75*4882a593Smuzhiyun        # tune -> task -> signature -> list of machines with that combination
76*4882a593Smuzhiyun        #
77*4882a593Smuzhiyun        # It is an error if any tune/task pair has more than one signature,
78*4882a593Smuzhiyun        # because that implies that the machines that caused those different
79*4882a593Smuzhiyun        # signatures do not agree on how to execute the task.
80*4882a593Smuzhiyun        tunes = {}
81*4882a593Smuzhiyun        # Preserve ordering of machines as chosen by the user.
82*4882a593Smuzhiyun        for machine in self.td['machines']:
83*4882a593Smuzhiyun            curr_sigs, tune2tasks = get_signatures(self.td['builddir'], failsafe=True, machine=machine)
84*4882a593Smuzhiyun            # Invert the tune -> [tasks] mapping.
85*4882a593Smuzhiyun            tasks2tune = {}
86*4882a593Smuzhiyun            for tune, tasks in tune2tasks.items():
87*4882a593Smuzhiyun                for task in tasks:
88*4882a593Smuzhiyun                    tasks2tune[task] = tune
89*4882a593Smuzhiyun            for task, sighash in curr_sigs.items():
90*4882a593Smuzhiyun                tunes.setdefault(tasks2tune[task], {}).setdefault(task, {}).setdefault(sighash, []).append(machine)
91*4882a593Smuzhiyun
92*4882a593Smuzhiyun        msg = []
93*4882a593Smuzhiyun        pruned = 0
94*4882a593Smuzhiyun        last_line_key = None
95*4882a593Smuzhiyun        # do_fetch, do_unpack, ..., do_build
96*4882a593Smuzhiyun        taskname_list = []
97*4882a593Smuzhiyun        if tunes:
98*4882a593Smuzhiyun            # The output below is most useful when we start with tasks that are at
99*4882a593Smuzhiyun            # the bottom of the dependency chain, i.e. those that run first. If
100*4882a593Smuzhiyun            # those tasks differ, the rest also does.
101*4882a593Smuzhiyun            #
102*4882a593Smuzhiyun            # To get an ordering of tasks, we do a topological sort of the entire
103*4882a593Smuzhiyun            # depgraph for the base configuration, then on-the-fly flatten that list by stripping
104*4882a593Smuzhiyun            # out the recipe names and removing duplicates. The base configuration
105*4882a593Smuzhiyun            # is not necessarily representative, but should be close enough. Tasks
106*4882a593Smuzhiyun            # that were not encountered get a default priority.
107*4882a593Smuzhiyun            depgraph = get_depgraph()
108*4882a593Smuzhiyun            depends = depgraph['tdepends']
109*4882a593Smuzhiyun            WHITE = 1
110*4882a593Smuzhiyun            GRAY = 2
111*4882a593Smuzhiyun            BLACK = 3
112*4882a593Smuzhiyun            color = {}
113*4882a593Smuzhiyun            found = set()
114*4882a593Smuzhiyun            def visit(task):
115*4882a593Smuzhiyun                color[task] = GRAY
116*4882a593Smuzhiyun                for dep in depends.get(task, ()):
117*4882a593Smuzhiyun                    if color.setdefault(dep, WHITE) == WHITE:
118*4882a593Smuzhiyun                        visit(dep)
119*4882a593Smuzhiyun                color[task] = BLACK
120*4882a593Smuzhiyun                pn, taskname = task.rsplit('.', 1)
121*4882a593Smuzhiyun                if taskname not in found:
122*4882a593Smuzhiyun                    taskname_list.append(taskname)
123*4882a593Smuzhiyun                    found.add(taskname)
124*4882a593Smuzhiyun            for task in depends.keys():
125*4882a593Smuzhiyun                if color.setdefault(task, WHITE) == WHITE:
126*4882a593Smuzhiyun                    visit(task)
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun        taskname_order = dict([(task, index) for index, task in enumerate(taskname_list) ])
129*4882a593Smuzhiyun        def task_key(task):
130*4882a593Smuzhiyun            pn, taskname = task.rsplit(':', 1)
131*4882a593Smuzhiyun            return (pn, taskname_order.get(taskname, len(taskname_list)), taskname)
132*4882a593Smuzhiyun
133*4882a593Smuzhiyun        for tune in sorted(tunes.keys()):
134*4882a593Smuzhiyun            tasks = tunes[tune]
135*4882a593Smuzhiyun            # As for test_signatures it would be nicer to sort tasks
136*4882a593Smuzhiyun            # by dependencies here, but that is harder because we have
137*4882a593Smuzhiyun            # to report on tasks from different machines, which might
138*4882a593Smuzhiyun            # have different dependencies. We resort to pruning the
139*4882a593Smuzhiyun            # output by reporting only one task per recipe if the set
140*4882a593Smuzhiyun            # of machines matches.
141*4882a593Smuzhiyun            #
142*4882a593Smuzhiyun            # "bitbake-diffsigs -t -s" is intelligent enough to print
143*4882a593Smuzhiyun            # diffs recursively, so often it does not matter that much
144*4882a593Smuzhiyun            # if we don't pick the underlying difference
145*4882a593Smuzhiyun            # here. However, sometimes recursion fails
146*4882a593Smuzhiyun            # (https://bugzilla.yoctoproject.org/show_bug.cgi?id=6428).
147*4882a593Smuzhiyun            #
148*4882a593Smuzhiyun            # To mitigate that a bit, we use a hard-coded ordering of
149*4882a593Smuzhiyun            # tasks that represents how they normally run and prefer
150*4882a593Smuzhiyun            # to print the ones that run first.
151*4882a593Smuzhiyun            for task in sorted(tasks.keys(), key=task_key):
152*4882a593Smuzhiyun                signatures = tasks[task]
153*4882a593Smuzhiyun                # do_build can be ignored: it is know to have
154*4882a593Smuzhiyun                # different signatures in some cases, for example in
155*4882a593Smuzhiyun                # the allarch ca-certificates due to RDEPENDS=openssl.
156*4882a593Smuzhiyun                # That particular dependency is marked via
157*4882a593Smuzhiyun                # SIGGEN_EXCLUDE_SAFE_RECIPE_DEPS, but still shows up
158*4882a593Smuzhiyun                # in the sstate signature hash because filtering it
159*4882a593Smuzhiyun                # out would be hard and running do_build multiple
160*4882a593Smuzhiyun                # times doesn't really matter.
161*4882a593Smuzhiyun                if len(signatures.keys()) > 1 and \
162*4882a593Smuzhiyun                   not task.endswith(':do_build'):
163*4882a593Smuzhiyun                    # Error!
164*4882a593Smuzhiyun                    #
165*4882a593Smuzhiyun                    # Sort signatures by machines, because the hex values don't mean anything.
166*4882a593Smuzhiyun                    # => all-arch adwaita-icon-theme:do_build: 1234... (beaglebone, qemux86) != abcdf... (qemux86-64)
167*4882a593Smuzhiyun                    #
168*4882a593Smuzhiyun                    # Skip the line if it is covered already by the predecessor (same pn, same sets of machines).
169*4882a593Smuzhiyun                    pn, taskname = task.rsplit(':', 1)
170*4882a593Smuzhiyun                    next_line_key = (pn, sorted(signatures.values()))
171*4882a593Smuzhiyun                    if next_line_key != last_line_key:
172*4882a593Smuzhiyun                        line = '   %s %s: ' % (tune, task)
173*4882a593Smuzhiyun                        line += ' != '.join(['%s (%s)' % (signature, ', '.join([m for m in signatures[signature]])) for
174*4882a593Smuzhiyun                                             signature in sorted(signatures.keys(), key=lambda s: signatures[s])])
175*4882a593Smuzhiyun                        last_line_key = next_line_key
176*4882a593Smuzhiyun                        msg.append(line)
177*4882a593Smuzhiyun                        # Randomly pick two mismatched signatures and remember how to invoke
178*4882a593Smuzhiyun                        # bitbake-diffsigs for them.
179*4882a593Smuzhiyun                        iterator = iter(signatures.items())
180*4882a593Smuzhiyun                        a = next(iterator)
181*4882a593Smuzhiyun                        b = next(iterator)
182*4882a593Smuzhiyun                        diffsig_machines = '(%s) != (%s)' % (', '.join(a[1]), ', '.join(b[1]))
183*4882a593Smuzhiyun                        diffsig_params = '-t %s %s -s %s %s' % (pn, taskname, a[0], b[0])
184*4882a593Smuzhiyun                    else:
185*4882a593Smuzhiyun                        pruned += 1
186*4882a593Smuzhiyun
187*4882a593Smuzhiyun        if msg:
188*4882a593Smuzhiyun            msg.insert(0, 'The machines have conflicting signatures for some shared tasks:')
189*4882a593Smuzhiyun            if pruned > 0:
190*4882a593Smuzhiyun                msg.append('')
191*4882a593Smuzhiyun                msg.append('%d tasks where not listed because some other task of the recipe already differed.' % pruned)
192*4882a593Smuzhiyun                msg.append('It is likely that differences from different recipes also have the same root cause.')
193*4882a593Smuzhiyun            msg.append('')
194*4882a593Smuzhiyun            # Explain how to investigate...
195*4882a593Smuzhiyun            msg.append('To investigate, run bitbake-diffsigs -t recipename taskname -s fromsig tosig.')
196*4882a593Smuzhiyun            cmd = 'bitbake-diffsigs %s' % diffsig_params
197*4882a593Smuzhiyun            msg.append('Example: %s in the last line' % diffsig_machines)
198*4882a593Smuzhiyun            msg.append('Command: %s' % cmd)
199*4882a593Smuzhiyun            # ... and actually do it automatically for that example, but without aborting
200*4882a593Smuzhiyun            # when that fails.
201*4882a593Smuzhiyun            try:
202*4882a593Smuzhiyun                output = check_command('Comparing signatures failed.', cmd).decode('utf-8')
203*4882a593Smuzhiyun            except RuntimeError as ex:
204*4882a593Smuzhiyun                output = str(ex)
205*4882a593Smuzhiyun            msg.extend(['   ' + line for line in output.splitlines()])
206*4882a593Smuzhiyun            self.fail('\n'.join(msg))
207