source: rtems-tools/tester/rt/check.py @ 3e14594

4.105
Last change on this file since 3e14594 was 3e14594, checked in by Chris Johns <chrisj@…>, on 07/06/16 at 07:58:56

Add rtems-bsp-builder.

This is a testing tool that builds BSPs with a range of configure options
for regression testing changes do not break the kernel code.

The builds are controlled by an INI file. The INI configuration has profiles
which define a specific set of architures and BSP to build. There are architectures
which contain BSPs and these further define the options needed to build the BSP.
There is also builds which define the variations each BSP is built with.

The build output can be pointed to any suitable disks so you can control where
the output ends up.

This initial release contains tiers and these are only seeded with something to
test with. It does not define the tiers.

Wanrings, object files and libraries are counted.

  • Property mode set to 100755
File size: 19.2 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2016 Chris Johns (chrisj@rtems.org)
4# All rights reserved.
5#
6# This file is part of the RTEMS Tools package in 'rtems-tools'.
7#
8# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions are met:
10#
11# 1. Redistributions of source code must retain the above copyright notice,
12# this list of conditions and the following disclaimer.
13#
14# 2. Redistributions in binary form must reproduce the above copyright notice,
15# this list of conditions and the following disclaimer in the documentation
16# and/or other materials provided with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29#
30
31from __future__ import print_function
32
33import argparse
34import datetime
35import operator
36import os
37import sys
38
39from rtemstoolkit import execute
40from rtemstoolkit import error
41from rtemstoolkit import log
42from rtemstoolkit import path
43from rtemstoolkit import version
44
45def rtems_version():
46    return version.version()
47
48class warnings_counter:
49
50    def __init__(self, rtems):
51        self.rtems = path.host(rtems)
52        self.reset()
53
54    def report(self):
55        str = ''
56        sw = sorted(self.warnings.items(), key = operator.itemgetter(1), reverse = True)
57        for w in sw:
58            str += ' %5d %s%s' % (w[1], w[0], os.linesep)
59        return str
60
61    def accumulate(self, total):
62        for w in self.warnings:
63            if w not in total.warnings:
64                total.warnings[w] = self.warnings[w]
65            else:
66                total.warnings[w] += self.warnings[w]
67        total.count += self.count
68
69    def get(self):
70        return self.count
71
72    def reset(self):
73        self.warnings = { }
74        self.count = 0
75
76    def output(self, text):
77        for l in text.splitlines():
78            if ' warning:' in l:
79                self.count += 1
80                ws = l.split(' ')
81                if len(ws) > 0:
82                    ws = ws[0].split(':')
83                    w = path.abspath(ws[0])
84                    w = w.replace(self.rtems, '')
85                    if path.isabspath(w):
86                        w = w[1:]
87                    w = '%s:%s:%s' % (w, ws[1], ws[2])
88                    if w not in self.warnings:
89                        self.warnings[w] = 0
90                    self.warnings[w] += 1
91        log.output(text)
92
93class configuration:
94
95    def __init__(self):
96        try:
97            import configparser
98        except:
99            import ConfigParser as configparser
100        self.config = configparser.ConfigParser()
101        self.name = None
102        self.archs = { }
103        self.builds = { }
104        self.profiles = { }
105
106    def __str__(self):
107        import pprint
108        s = self.name + os.linesep
109        s += 'Archs:' + os.linesep + \
110             pprint.pformat(self.archs, indent = 1, width = 80) + os.linesep
111        s += 'Builds:' + os.linesep + \
112             pprint.pformat(self.builds, indent = 1, width = 80) + os.linesep
113        s += 'Profiles:' + os.linesep + \
114             pprint.pformat(self.profiles, indent = 1, width = 80) + os.linesep
115        return s
116
117    def _get_item(self, section, label, err = True):
118        try:
119            rec = self.config.get(section, label).replace(os.linesep, ' ')
120            return rec
121        except:
122            if err:
123                raise error.general('config: no %s found in %s' % (label, section))
124        return None
125
126    def _comma_list(self, section, label, error = True):
127        items = self._get_item(section, label, error)
128        if items is None:
129            return []
130        return sorted(set([a.strip() for a in items.split(',')]))
131
132    def load(self, name):
133        if not path.exists(name):
134            raise error.general('config: cannot read configuration: %s' % (name))
135        self.name = name
136        self.config.read(name)
137        archs = []
138        self.profiles['profiles'] = self._comma_list('profiles', 'profiles', error = False)
139        if len(self.profiles['profiles']) == 0:
140            self.profiles['profiles'] = ['tier_%d' % (t) for t in range(1,4)]
141        for p in self.profiles['profiles']:
142            profile = {}
143            profile['name'] = p
144            profile['archs'] = self._comma_list(profile['name'], 'archs')
145            archs += profile['archs']
146            for arch in profile['archs']:
147                bsps = 'bsps_%s' % (arch)
148                profile[bsps] = self._comma_list(profile['name'], bsps)
149            self.profiles[profile['name']] = profile
150        for a in set(archs):
151            arch = {}
152            arch['excludes'] = self._comma_list(a, 'excludes', error = False)
153            arch['bsps'] = self._comma_list(a, 'bsps', error = False)
154            for b in arch['bsps']:
155                arch[b] = {}
156                arch[b]['bspopts'] = self._comma_list(a, 'bspopts_%s' % (b), error = False)
157                arch[b]['config'] = self._comma_list(a, 'config_%s' % (b), error = False)
158            self.archs[a] = arch
159        builds = {}
160        builds['default'] = self._get_item('builds', 'default').split()
161        builds['variations'] = self._comma_list('builds', 'variations')
162        builds['var_options'] = {}
163        for v in builds['variations']:
164            builds['var_options'][v] = self._get_item('builds', v).split()
165        self.builds = builds
166
167    def variations(self):
168        return self.builds['variations']
169
170    def excludes(self, arch):
171        return self.archs[arch]['excludes']
172
173    def archs(self):
174        return sorted(self.archs.keys())
175
176    def arch_present(self, arch):
177        return arch in self.archs
178
179    def arch_bsps(self, arch):
180        return sorted(self.archs[arch]['bsps'])
181
182    def bsp_present(self, arch, bsp):
183        return bsp in self.archs[arch]['bsps']
184
185    def bspopts(self, arch, bsp):
186        return self.archs[arch][bsp]['bspopts']
187
188    def defaults(self):
189        return self.builds['default']
190
191    def variant_options(self, variant):
192        if variant in self.builds['var_options']:
193            self.builds['var_options'][variant]
194        return []
195
196    def profile_present(self, profile):
197        return profile in self.profiles
198
199    def profile_archs(self, profile):
200        return self.profiles[profile]['archs']
201
202    def profile_arch_bsps(self, profile, arch):
203        return self.profiles[profile]['bsps_%s' % (arch)]
204
205class build:
206
207    def __init__(self, config, version, prefix, tools, rtems, build_dir, options):
208        self.config = config
209        self.build_dir = build_dir
210        self.rtems_version = version
211        self.prefix = prefix
212        self.tools = tools
213        self.rtems = rtems
214        self.options = options
215        self.errors = { 'configure': 0,
216                        'build':     0,
217                        'tests':     0 }
218        self.counts = { 'h'        : 0,
219                        'exes'     : 0,
220                        'objs'     : 0,
221                        'libs'     : 0 }
222        self.warnings = warnings_counter(rtems)
223        if not path.exists(path.join(rtems, 'configure')) or \
224           not path.exists(path.join(rtems, 'Makefile.in')) or \
225           not path.exists(path.join(rtems, 'cpukit')):
226            raise error.general('RTEMS source path does not look like RTEMS')
227
228    def _error_str(self):
229        return 'Status: configure:%d build:%d' % \
230            (self.errors['configure'], self.errors['build'])
231
232    def _path(self, arch, bsp):
233        return path.join(self.build_dir, arch, bsp)
234
235    def _archs(self, build_data):
236        return sorted(build_data.keys())
237
238    def _bsps(self, arch):
239        return self.config.arch_bsps(arch)
240
241    def _variations(self, arch, bsp):
242        vars = self.config.variations()
243        for v in self.config.excludes(arch):
244            if v in vars:
245                vars.remove(v)
246        return vars
247
248    def _arch_bsp_dir_make(self, arch, bsp):
249        if not path.exists(self._path(arch, bsp)):
250            path.mkdir(self._path(arch, bsp))
251
252    def _arch_bsp_dir_clean(self, arch, bsp):
253        if path.exists(self._path(arch, bsp)):
254            path.removeall(self._path(arch, bsp))
255
256    def _config_command(self, commands, arch, bsp):
257        cmd = [path.join(self.rtems, 'configure')]
258        commands += self.config.bspopts(arch, bsp)
259        for c in commands:
260            c = c.replace('@PREFIX@', self.prefix)
261            c = c.replace('@RTEMS_VERSION@', self.rtems_version)
262            c = c.replace('@ARCH@', arch)
263            c = c.replace('@BSP@', bsp)
264            cmd += [c]
265        return ' '.join(cmd)
266
267    def _build_set(self, variations):
268        build_set = { }
269        bs = self.config.defaults()
270        for var in variations:
271            build_set[var] = bs + self.config.variant_options(var)
272        return build_set
273
274    def _build_dir(self, arch, bsp, build):
275        return path.join(self._path(arch, bsp), build)
276
277    def _count_files(self, arch, bsp, build):
278        counts = { 'h'    : 0,
279                   'exes' : 0,
280                   'objs' : 0,
281                   'libs' : 0 }
282        for root, dirs, files in os.walk(self._build_dir(arch, bsp, build)):
283            for file in files:
284                if file.endswith('.exe'):
285                    counts['exes'] += 1
286                elif file.endswith('.o'):
287                    counts['objs'] += 1
288                elif file.endswith('.a'):
289                    counts['libs'] += 1
290                elif file.endswith('.h'):
291                    counts['h'] += 1
292        for f in self.counts:
293            if f in counts:
294                self.counts[f] = counts[f]
295        return counts
296
297    def build_arch_bsp(self, arch, bsp):
298        if not self.config.bsp_present(arch, bsp):
299            raise error.general('BSP not found: %s/%s' % (arch, bsp))
300        log.output('-' * 70)
301        log.notice('] BSP: %s/%s' % (arch, bsp))
302        log.notice('. Creating: %s' % (self._path(arch, bsp)))
303        self._arch_bsp_dir_clean(arch, bsp)
304        self._arch_bsp_dir_make(arch, bsp)
305        variations = self._variations(arch, bsp)
306        build_set = self._build_set(variations)
307        bsp_start = datetime.datetime.now()
308        bsp_warnings = warnings_counter(self.rtems)
309        env_path = os.environ['PATH']
310        os.environ['PATH'] = path.host(path.join(self.tools, 'bin')) + \
311                             os.pathsep + os.environ['PATH']
312        for bs in sorted(build_set.keys()):
313            warnings = warnings_counter(self.rtems)
314            start = datetime.datetime.now()
315            log.output('- ' * 35)
316            log.notice('. Configuring: %s' % (bs))
317            try:
318                bpath = self._build_dir(arch, bsp, bs)
319                path.mkdir(bpath)
320                cmd = self._config_command(build_set[bs], arch, bsp)
321                e = execute.capture_execution(log = warnings)
322                log.output('run: ' + cmd)
323                if self.options['dry-run']:
324                    exit_code = 0
325                else:
326                    exit_code, proc, output = e.shell(cmd, cwd = path.host(bpath))
327                if exit_code != 0:
328                    self.errors['configure'] += 1
329                    log.notice('- Configure failed: %s' % (bs))
330                    log.output('cmd failed: %s' % (cmd))
331                    if self.options['stop-on-error']:
332                        raise error.general('Configuring %s failed' % (bs))
333                else:
334                    log.notice('. Building: %s' % (bs))
335                    cmd = 'make'
336                    if 'jobs' in self.options:
337                        cmd += ' -j %s' % (self.options['jobs'])
338                    log.output('run: ' + cmd)
339                    if self.options['dry-run']:
340                        exit_code = 0
341                    else:
342                        exit_code, proc, output = e.shell(cmd, cwd = path.host(bpath))
343                    if exit_code != 0:
344                        self.errors['build'] += 1
345                        log.notice('- FAIL: %s: %s' % (bs, self._error_str()))
346                        log.output('cmd failed: %s' % (cmd))
347                        if self.options['stop-on-error']:
348                            raise error.general('Building %s failed' % (bs))
349                    files = self._count_files(arch, bsp, bs)
350                    log.notice('+ Pass: %s: warnings:%d  exes:%d  objs:%s  libs:%d' % \
351                               (bs, warnings.get(),
352                                files['exes'], files['objs'], files['libs']))
353                    log.notice('  %s' % (self._error_str()))
354            finally:
355                end = datetime.datetime.now()
356                if not self.options['no-clean']:
357                    log.notice('. Cleaning: %s' % (self._build_dir(arch, bsp, bs)))
358                    path.removeall(self._build_dir(arch, bsp, bs))
359            log.notice('^ Time %s' % (str(end - start)))
360            log.output('Warnings Report:')
361            log.output(warnings.report())
362            warnings.accumulate(bsp_warnings)
363            warnings.accumulate(self.warnings)
364        bsp_end = datetime.datetime.now()
365        log.notice('^ BSP Time %s' % (str(bsp_end - bsp_start)))
366        log.output('BSP Warnings Report:')
367        log.output(bsp_warnings.report())
368        os.environ['PATH'] = env_path
369
370    def build_arch(self, arch):
371        start = datetime.datetime.now()
372        log.output('=' * 70)
373        log.notice(']] Architecture: %s' % (arch))
374        if not self.confif.arch_present(arch):
375            raise error.general('Architecture not found: %s' % (arch))
376        for bsp in self._bsps(arch):
377            self.build_arch_bsp(arch, bsp)
378        log.notice('^ Architecture Time %s' % (str(end - start)))
379        log.notice('  warnings:%d  exes:%d  objs:%s  libs:%d' % \
380                   self.warnings.get(), self.counts['exes'],
381                   self.counts['objs'], self.counts['libs'])
382        log.output('Architecture Warnings:')
383        log.output(self.warnings.report())
384
385    def build(self):
386        for arch in self.config.archs():
387            self.build_arch(arch)
388        log.notice('^ Profile Time %s' % (str(end - start)))
389        log.notice('+  warnings:%d  exes:%d  objs:%s  libs:%d' % \
390                   self.warnings.get(), self.counts['exes'],
391                   self.counts['objs'], self.counts['libs'])
392        log.output('Profile Warnings:')
393        log.output(self.warnings.report())
394
395    def build_profile(self, profile):
396        if not self.config.profile_present(profile):
397            raise error.general('BSP not found: %s/%s' % (arch, bsp))
398        start = datetime.datetime.now()
399        log.notice(']] Profile: %s' % (profile))
400        for arch in self.config.profile_archs(profile):
401            for bsp in self.config.profile_arch_bsps(profile, arch):
402                self.build_arch_bsp(arch, bsp)
403        end = datetime.datetime.now()
404        log.notice('^ Profile Time %s' % (str(end - start)))
405        log.notice('  warnings:%d  exes:%d  objs:%d  libs:%d' % \
406                   (self.warnings.get(), self.counts['exes'],
407                    self.counts['objs'], self.counts['libs']))
408        log.output('Profile Warnings:')
409        log.output(self.warnings.report())
410
411def run_args(args):
412    try:
413        #
414        # On Windows MSYS2 prepends a path to itself to the environment
415        # path. This means the RTEMS specific automake is not found and which
416        # breaks the bootstrap. We need to remove the prepended path. Also
417        # remove any ACLOCAL paths from the environment.
418        #
419        if os.name == 'nt':
420            cspath = os.environ['PATH'].split(os.pathsep)
421            if 'msys' in cspath[0] and cspath[0].endswith('bin'):
422                os.environ['PATH'] = os.pathsep.join(cspath[1:])
423
424        top = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
425        profile = 'tier-1'
426        prefix = '/opt/rtems/%s' % (rtems_version())
427        tools = prefix
428        build_dir = 'bsp-builds'
429        logf = 'bsp-build-%s.txt' % (datetime.datetime.now().strftime('%Y%m%d-%H%M%S'))
430        config_file = path.join(top, 'share', 'rtems', 'tester', 'rtems', 'rtems-bsps.ini')
431
432        argsp = argparse.ArgumentParser()
433        argsp.add_argument('--prefix', help = 'Prefix to build the BSP.', type = str)
434        argsp.add_argument('--rtems-tools', help = 'The RTEMS tools directory.', type = str)
435        argsp.add_argument('--rtems', help = 'The RTEMS source tree.', type = str)
436        argsp.add_argument('--build-path', help = 'Path to build in.', type = str)
437        argsp.add_argument('--log', help = 'Log file.', type = str)
438        argsp.add_argument('--stop-on-error', help = 'Stop on an error.', action = 'store_true')
439        argsp.add_argument('--no-clean', help = 'Do not clean the build output.', action = 'store_true')
440        argsp.add_argument('--arch', help = 'Build the specific architecture.', type = str)
441        argsp.add_argument('--bsp', help = 'Build the specific BSP.', type = str)
442        argsp.add_argument('--dry-run', help = 'Do not run the actual builds.', action = 'store_true')
443
444        opts = argsp.parse_args(args[1:])
445        if opts.log is not None:
446            logf = opts.log
447        log.default = log.log([logf])
448        log.notice('RTEMS Tools Project - RTEMS Kernel Check, %s' % (version.str()))
449        if opts.rtems is None:
450            raise error.general('No RTEMS source provided on the command line')
451        if opts.prefix is not None:
452            prefix = path.shell(opts.prefix)
453        if opts.rtems_tools is not None:
454            tools = path.shell(opts.rtems_tools)
455        if opts.build_path is not None:
456            build_dir = path.shell(opts.build_path)
457        if opts.bsp is not None and opts.arch is None:
458            raise error.general('BSP provided but no architecture')
459
460        config = configuration()
461        config.load(config_file)
462
463        options = { 'stop-on-error' : opts.stop_on_error,
464                    'no-clean'      : opts.no_clean,
465                    'dry-run'       : opts.dry_run,
466                    'jobs'          : 8 }
467
468        b = build(config, rtems_version(), prefix, tools, path.shell(opts.rtems), build_dir, options)
469        if opts.arch is not None:
470            if opts.bsp is not None:
471                b.build_arch_bsp(opts.arch, opts.bsp)
472            else:
473                b.build_arch(opts.arch)
474        else:
475            b.build_profile(profile)
476
477    except error.general as gerr:
478        print(gerr)
479        print('BSP Build FAILED', file = sys.stderr)
480        sys.exit(1)
481    except error.internal as ierr:
482        print(ierr)
483        print('BSP Build FAILED', file = sys.stderr)
484        sys.exit(1)
485    except error.exit as eerr:
486        pass
487    except KeyboardInterrupt:
488        log.notice('abort: user terminated')
489        sys.exit(1)
490    sys.exit(0)
491
492def run():
493    run_args(sys.argv)
494
495if __name__ == "__main__":
496    run()
Note: See TracBrowser for help on using the repository browser.