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

4.105
Last change on this file since a252faf was a252faf, checked in by Chris Johns <chrisj@…>, on 08/30/16 at 08:10:17

bsp-builder: Results format fixes.

  • Property mode set to 100755
File size: 23.5 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
39try:
40    import configparser
41except:
42    import ConfigParser as configparser
43
44from rtemstoolkit import execute
45from rtemstoolkit import error
46from rtemstoolkit import log
47from rtemstoolkit import path
48from rtemstoolkit import version
49
50def rtems_version():
51    return version.version()
52
53class warnings_counter:
54
55    def __init__(self, rtems):
56        self.rtems = path.host(rtems)
57        self.reset()
58
59    def report(self):
60        str = ''
61        sw = sorted(self.warnings.items(), key = operator.itemgetter(1), reverse = True)
62        for w in sw:
63            str += ' %5d %s%s' % (w[1], w[0], os.linesep)
64        return str
65
66    def accumulate(self, total):
67        for w in self.warnings:
68            if w not in total.warnings:
69                total.warnings[w] = self.warnings[w]
70            else:
71                total.warnings[w] += self.warnings[w]
72        total.count += self.count
73
74    def get(self):
75        return self.count
76
77    def reset(self):
78        self.warnings = { }
79        self.count = 0
80
81    def output(self, text):
82        for l in text.splitlines():
83            if ' warning:' in l:
84                self.count += 1
85                ws = l.split(' ')
86                if len(ws) > 0:
87                    ws = ws[0].split(':')
88                    w = path.abspath(ws[0])
89                    w = w.replace(self.rtems, '')
90                    if path.isabspath(w):
91                        w = w[1:]
92                    #
93                    # Ignore compiler option warnings.
94                    #
95                    if len(ws) >= 3:
96                        w = '%s:%s:%s' % (w, ws[1], ws[2])
97                        if w not in self.warnings:
98                            self.warnings[w] = 0
99                        self.warnings[w] += 1
100        log.output(text)
101
102class results:
103
104    def __init__(self):
105        self.passes = []
106        self.fails = []
107
108    def _arch_bsp(self, arch, bsp):
109        return '%s/%s' % (arch, bsp)
110
111    def add(self, good, arch, bsp, configure, warnings):
112        if good:
113            self.passes += [(arch, bsp, configure, warnings)]
114        else:
115            self.fails += [(arch, bsp, configure, 0)]
116
117    def report(self):
118        log.notice('* Passes: %d   Failures: %d' %
119                   (len(self.passes), len(self.fails)))
120        log.output()
121        log.output('Build Report')
122        log.output('   Passes: %d   Failures: %d' %
123                   (len(self.passes), len(self.fails)))
124        log.output(' Failures:')
125        if len(self.fails) == 0:
126            log.output('None')
127        else:
128            max_col = 0
129            for f in self.fails:
130                arch_bsp = self._arch_bsp(f[0], f[1])
131                if len(arch_bsp) > max_col:
132                    max_col = len(arch_bsp)
133            for f in self.fails:
134                config_cmd = f[2]
135                config_at = config_cmd.find('configure')
136                if config_at != -1:
137                    config_cmd = config_cmd[config_at:]
138                log.output(' %*s:  %s' % (max_col + 2,
139                                          self._arch_bsp(f[0], f[1]),
140                                          config_cmd))
141        log.output(' Passes:')
142        if len(self.passes) == 0:
143            log.output('None')
144        else:
145            max_col = 0
146            for f in self.passes:
147                arch_bsp = self._arch_bsp(f[0], f[1])
148                if len(arch_bsp) > max_col:
149                    max_col = len(arch_bsp)
150            for f in self.passes:
151                config_cmd = f[2]
152                config_at = config_cmd.find('configure')
153                if config_at != -1:
154                    config_cmd = config_cmd[config_at:]
155                log.output(' %*s:  %5d  %s' % (max_col + 2,
156                                               self._arch_bsp(f[0], f[1]),
157                                               f[3],
158                                               config_cmd))
159
160class configuration:
161
162    def __init__(self):
163        self.config = configparser.ConfigParser()
164        self.name = None
165        self.archs = { }
166        self.builds = { }
167        self.profiles = { }
168
169    def __str__(self):
170        import pprint
171        s = self.name + os.linesep
172        s += 'Archs:' + os.linesep + \
173             pprint.pformat(self.archs, indent = 1, width = 80) + os.linesep
174        s += 'Builds:' + os.linesep + \
175             pprint.pformat(self.builds, indent = 1, width = 80) + os.linesep
176        s += 'Profiles:' + os.linesep + \
177             pprint.pformat(self.profiles, indent = 1, width = 80) + os.linesep
178        return s
179
180    def _get_item(self, section, label, err = True):
181        try:
182            rec = self.config.get(section, label).replace(os.linesep, ' ')
183            return rec
184        except:
185            if err:
186                raise error.general('config: no %s found in %s' % (label, section))
187        return None
188
189    def _get_items(self, section, err = True):
190        try:
191            items = self.config.items(section)
192            return items
193        except:
194            if err:
195                raise error.general('config: section %s not found' % (section))
196        return []
197
198    def _comma_list(self, section, label, error = True):
199        items = self._get_item(section, label, error)
200        if items is None:
201            return []
202        return sorted(set([a.strip() for a in items.split(',')]))
203
204    def load(self, name):
205        if not path.exists(name):
206            raise error.general('config: cannot read configuration: %s' % (name))
207        self.name = name
208        try:
209            self.config.read(name)
210        except configparser.ParsingError as ce:
211            raise error.general('config: %s' % (ce))
212        archs = []
213        self.profiles['profiles'] = self._comma_list('profiles', 'profiles', error = False)
214        if len(self.profiles['profiles']) == 0:
215            self.profiles['profiles'] = ['tier_%d' % (t) for t in range(1,4)]
216        for p in self.profiles['profiles']:
217            profile = {}
218            profile['name'] = p
219            profile['archs'] = self._comma_list(profile['name'], 'archs')
220            archs += profile['archs']
221            for arch in profile['archs']:
222                bsps = 'bsps_%s' % (arch)
223                profile[bsps] = self._comma_list(profile['name'], bsps)
224            self.profiles[profile['name']] = profile
225        for a in set(archs):
226            arch = {}
227            arch['excludes'] = {}
228            for exclude in self._comma_list(a, 'exclude', error = False):
229                arch['excludes'][exclude] = ['all']
230            for i in self._get_items(a, False):
231                if i[0].startswith('exclude_'):
232                    exclude = i[0][len('exclude_'):]
233                    if exclude not in arch['excludes']:
234                        arch['excludes'][exclude] = []
235                    arch['excludes'][exclude] += sorted(set([b.strip() for b in i[1].split(',')]))
236            arch['bsps'] = self._comma_list(a, 'bsps', error = False)
237            for b in arch['bsps']:
238                arch[b] = {}
239                arch[b]['bspopts'] = self._comma_list(a, 'bspopts_%s' % (b), error = False)
240            self.archs[a] = arch
241        builds = {}
242        builds['default'] = self._get_item('builds', 'default').split()
243        builds['variations'] = self._comma_list('builds', 'variations')
244        builds['var_options'] = {}
245        for v in builds['variations']:
246            builds['var_options'][v] = self._get_item('builds', v).split()
247        self.builds = builds
248
249    def variations(self):
250        return self.builds['variations']
251
252    def excludes(self, arch):
253        excludes = self.archs[arch]['excludes'].keys()
254        for exclude in self.archs[arch]['excludes']:
255            if 'all' not in self.archs[arch]['excludes'][exclude]:
256                excludes.remove(exclude)
257        return sorted(excludes)
258
259    def archs(self):
260        return sorted(self.archs.keys())
261
262    def arch_present(self, arch):
263        return arch in self.archs
264
265    def arch_bsps(self, arch):
266        return sorted(self.archs[arch]['bsps'])
267
268    def bsp_present(self, arch, bsp):
269        return bsp in self.archs[arch]['bsps']
270
271    def bsp_excludes(self, arch, bsp):
272        excludes = self.archs[arch]['excludes'].keys()
273        for exclude in self.archs[arch]['excludes']:
274            if bsp not in self.archs[arch]['excludes'][exclude]:
275                excludes.remove(exclude)
276        return sorted(excludes)
277
278    def bspopts(self, arch, bsp):
279        return self.archs[arch][bsp]['bspopts']
280
281    def defaults(self):
282        return self.builds['default']
283
284    def variant_options(self, variant):
285        if variant in self.builds['var_options']:
286            return self.builds['var_options'][variant]
287        return []
288
289    def profile_present(self, profile):
290        return profile in self.profiles
291
292    def profile_archs(self, profile):
293        return self.profiles[profile]['archs']
294
295    def profile_arch_bsps(self, profile, arch):
296        return self.profiles[profile]['bsps_%s' % (arch)]
297
298class build:
299
300    def __init__(self, config, version, prefix, tools, rtems, build_dir, options):
301        self.config = config
302        self.build_dir = build_dir
303        self.rtems_version = version
304        self.prefix = prefix
305        self.tools = tools
306        self.rtems = rtems
307        self.options = options
308        self.errors = { 'configure': 0,
309                        'build':     0,
310                        'tests':     0 }
311        self.counts = { 'h'        : 0,
312                        'exes'     : 0,
313                        'objs'     : 0,
314                        'libs'     : 0 }
315        self.warnings = warnings_counter(rtems)
316        self.results = results()
317        if not path.exists(path.join(rtems, 'configure')) or \
318           not path.exists(path.join(rtems, 'Makefile.in')) or \
319           not path.exists(path.join(rtems, 'cpukit')):
320            raise error.general('RTEMS source path does not look like RTEMS')
321
322    def _error_str(self):
323        return 'Status: configure:%d build:%d' % \
324            (self.errors['configure'], self.errors['build'])
325
326    def _path(self, arch, bsp):
327        return path.join(self.build_dir, arch, bsp)
328
329    def _archs(self, build_data):
330        return sorted(build_data.keys())
331
332    def _bsps(self, arch):
333        return self.config.arch_bsps(arch)
334
335    def _variations(self, arch, bsp):
336        def _match(var, vars):
337            matches = []
338            for v in vars:
339                if var in v.split('-'):
340                    matches += [v]
341            return matches
342
343        vars = self.config.variations()
344        for v in self.config.excludes(arch):
345            for m in _match(v, vars):
346                vars.remove(m)
347        for v in self.config.bsp_excludes(arch, bsp):
348            for m in _match(v, vars):
349                vars.remove(m)
350        return vars
351
352    def _arch_bsp_dir_make(self, arch, bsp):
353        if not path.exists(self._path(arch, bsp)):
354            path.mkdir(self._path(arch, bsp))
355
356    def _arch_bsp_dir_clean(self, arch, bsp):
357        if path.exists(self._path(arch, bsp)):
358            path.removeall(self._path(arch, bsp))
359
360    def _config_command(self, commands, arch, bsp):
361        cmd = [path.join(self.rtems, 'configure')]
362        commands += self.config.bspopts(arch, bsp)
363        for c in commands:
364            c = c.replace('@PREFIX@', self.prefix)
365            c = c.replace('@RTEMS_VERSION@', self.rtems_version)
366            c = c.replace('@ARCH@', arch)
367            c = c.replace('@BSP@', bsp)
368            cmd += [c]
369        return ' '.join(cmd)
370
371    def _build_set(self, variations):
372        build_set = { }
373        bs = self.config.defaults()
374        for var in variations:
375            build_set[var] = bs + self.config.variant_options(var)
376        return build_set
377
378    def _build_dir(self, arch, bsp, build):
379        return path.join(self._path(arch, bsp), build)
380
381    def _count_files(self, arch, bsp, build):
382        counts = { 'h'    : 0,
383                   'exes' : 0,
384                   'objs' : 0,
385                   'libs' : 0 }
386        for root, dirs, files in os.walk(self._build_dir(arch, bsp, build)):
387            for file in files:
388                if file.endswith('.exe'):
389                    counts['exes'] += 1
390                elif file.endswith('.o'):
391                    counts['objs'] += 1
392                elif file.endswith('.a'):
393                    counts['libs'] += 1
394                elif file.endswith('.h'):
395                    counts['h'] += 1
396        for f in self.counts:
397            if f in counts:
398                self.counts[f] = counts[f]
399        return counts
400
401    def build_arch_bsp(self, arch, bsp):
402        if not self.config.bsp_present(arch, bsp):
403            raise error.general('BSP not found: %s/%s' % (arch, bsp))
404        log.output('-' * 70)
405        log.notice('] BSP: %s/%s' % (arch, bsp))
406        log.notice('. Creating: %s' % (self._path(arch, bsp)))
407        self._arch_bsp_dir_clean(arch, bsp)
408        self._arch_bsp_dir_make(arch, bsp)
409        variations = self._variations(arch, bsp)
410        build_set = self._build_set(variations)
411        bsp_start = datetime.datetime.now()
412        bsp_warnings = warnings_counter(self.rtems)
413        env_path = os.environ['PATH']
414        os.environ['PATH'] = path.host(path.join(self.tools, 'bin')) + \
415                             os.pathsep + os.environ['PATH']
416        for bs in sorted(build_set.keys()):
417            warnings = warnings_counter(self.rtems)
418            start = datetime.datetime.now()
419            log.output('- ' * 35)
420            log.notice('. Configuring: %s' % (bs))
421            try:
422                result = '+ Pass'
423                bpath = self._build_dir(arch, bsp, bs)
424                path.mkdir(bpath)
425                config_cmd = self._config_command(build_set[bs], arch, bsp)
426                cmd = config_cmd
427                e = execute.capture_execution(log = warnings)
428                log.output('run: ' + cmd)
429                if self.options['dry-run']:
430                    exit_code = 0
431                else:
432                    exit_code, proc, output = e.shell(cmd, cwd = path.host(bpath))
433                if exit_code != 0:
434                    result = '- FAIL'
435                    self.errors['configure'] += 1
436                    log.notice('- Configure failed: %s' % (bs))
437                    log.output('cmd failed: %s' % (cmd))
438                    if self.options['stop-on-error']:
439                        raise error.general('Configuring %s failed' % (bs))
440                else:
441                    log.notice('. Building: %s' % (bs))
442                    cmd = 'make'
443                    if 'jobs' in self.options:
444                        cmd += ' -j %s' % (self.options['jobs'])
445                    log.output('run: ' + cmd)
446                    if self.options['dry-run']:
447                        exit_code = 0
448                    else:
449                        exit_code, proc, output = e.shell(cmd, cwd = path.host(bpath))
450                    if exit_code != 0:
451                        result = '- FAIL'
452                        self.errors['build'] += 1
453                        log.notice('- FAIL: %s: %s' % (bs, self._error_str()))
454                        log.output('cmd failed: %s' % (cmd))
455                        if self.options['stop-on-error']:
456                            raise error.general('Building %s failed' % (bs))
457                    files = self._count_files(arch, bsp, bs)
458                log.notice('%s: %s: warnings:%d  exes:%d  objs:%s  libs:%d' % \
459                           (result, bs, warnings.get(),
460                            files['exes'], files['objs'], files['libs']))
461                log.notice('  %s' % (self._error_str()))
462                self.results.add(result[0] == '+', arch, bsp, config_cmd, warnings.get())
463            finally:
464                end = datetime.datetime.now()
465                if not self.options['no-clean']:
466                    log.notice('. Cleaning: %s' % (self._build_dir(arch, bsp, bs)))
467                    path.removeall(self._build_dir(arch, bsp, bs))
468            log.notice('^ Time %s' % (str(end - start)))
469            log.output('Warnings Report:')
470            log.output(warnings.report())
471            warnings.accumulate(bsp_warnings)
472            warnings.accumulate(self.warnings)
473        bsp_end = datetime.datetime.now()
474        log.notice('^ BSP Time %s' % (str(bsp_end - bsp_start)))
475        log.output('BSP Warnings Report:')
476        log.output(bsp_warnings.report())
477        os.environ['PATH'] = env_path
478
479    def build_arch(self, arch):
480        start = datetime.datetime.now()
481        log.output('=' * 70)
482        log.notice(']] Architecture: %s' % (arch))
483        if not self.config.arch_present(arch):
484            raise error.general('Architecture not found: %s' % (arch))
485        for bsp in self._bsps(arch):
486            self.build_arch_bsp(arch, bsp)
487        log.notice('^ Architecture Time %s' % (str(end - start)))
488        log.notice('  warnings:%d  exes:%d  objs:%s  libs:%d' % \
489                   self.warnings.get(), self.counts['exes'],
490                   self.counts['objs'], self.counts['libs'])
491        log.output('Architecture Warnings:')
492        log.output(self.warnings.report())
493
494    def build(self):
495        for arch in self.config.archs():
496            self.build_arch(arch)
497        log.notice('^ Profile Time %s' % (str(end - start)))
498        log.notice('+  warnings:%d  exes:%d  objs:%s  libs:%d' % \
499                   self.warnings.get(), self.counts['exes'],
500                   self.counts['objs'], self.counts['libs'])
501        log.output('Profile Warnings:')
502        log.output(self.warnings.report())
503
504    def build_profile(self, profile):
505        if not self.config.profile_present(profile):
506            raise error.general('BSP not found: %s/%s' % (arch, bsp))
507        start = datetime.datetime.now()
508        log.notice(']] Profile: %s' % (profile))
509        for arch in self.config.profile_archs(profile):
510            for bsp in self.config.profile_arch_bsps(profile, arch):
511                self.build_arch_bsp(arch, bsp)
512        end = datetime.datetime.now()
513        log.notice('^ Profile Time %s' % (str(end - start)))
514        log.notice('  warnings:%d  exes:%d  objs:%d  libs:%d' % \
515                   (self.warnings.get(), self.counts['exes'],
516                    self.counts['objs'], self.counts['libs']))
517        log.output('Profile Warnings:')
518        log.output(self.warnings.report())
519
520def run_args(args):
521    b = None
522    ec = 0
523    try:
524        #
525        # On Windows MSYS2 prepends a path to itself to the environment
526        # path. This means the RTEMS specific automake is not found and which
527        # breaks the bootstrap. We need to remove the prepended path. Also
528        # remove any ACLOCAL paths from the environment.
529        #
530        if os.name == 'nt':
531            cspath = os.environ['PATH'].split(os.pathsep)
532            if 'msys' in cspath[0] and cspath[0].endswith('bin'):
533                os.environ['PATH'] = os.pathsep.join(cspath[1:])
534
535        top = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
536        prefix = '/opt/rtems/%s' % (rtems_version())
537        tools = prefix
538        build_dir = 'bsp-builds'
539        logf = 'bsp-build-%s.txt' % (datetime.datetime.now().strftime('%Y%m%d-%H%M%S'))
540        config_file = path.join(top, 'share', 'rtems', 'tester', 'rtems', 'rtems-bsps.ini')
541        if not path.exists(config_file):
542            config_file = path.join(top, 'tester', 'rtems', 'rtems-bsps.ini')
543
544        argsp = argparse.ArgumentParser()
545        argsp.add_argument('--prefix', help = 'Prefix to build the BSP.', type = str)
546        argsp.add_argument('--rtems-tools', help = 'The RTEMS tools directory.', type = str)
547        argsp.add_argument('--rtems', help = 'The RTEMS source tree.', type = str)
548        argsp.add_argument('--build-path', help = 'Path to build in.', type = str)
549        argsp.add_argument('--log', help = 'Log file.', type = str)
550        argsp.add_argument('--stop-on-error', help = 'Stop on an error.', action = 'store_true')
551        argsp.add_argument('--no-clean', help = 'Do not clean the build output.', action = 'store_true')
552        argsp.add_argument('--profiles', help = 'Build the listed profiles.', type = str, default = 'tier-1')
553        argsp.add_argument('--arch', help = 'Build the specific architecture.', type = str)
554        argsp.add_argument('--bsp', help = 'Build the specific BSP.', type = str)
555        argsp.add_argument('--dry-run', help = 'Do not run the actual builds.', action = 'store_true')
556
557        opts = argsp.parse_args(args[1:])
558        if opts.log is not None:
559            logf = opts.log
560        log.default = log.log([logf])
561        log.notice('RTEMS Tools Project - RTEMS Kernel BSP Builder, %s' % (version.str()))
562        if opts.rtems is None:
563            raise error.general('No RTEMS source provided on the command line')
564        if opts.prefix is not None:
565            prefix = path.shell(opts.prefix)
566        if opts.rtems_tools is not None:
567            tools = path.shell(opts.rtems_tools)
568        if opts.build_path is not None:
569            build_dir = path.shell(opts.build_path)
570        if opts.bsp is not None and opts.arch is None:
571            raise error.general('BSP provided but no architecture')
572
573        config = configuration()
574        config.load(config_file)
575
576        options = { 'stop-on-error' : opts.stop_on_error,
577                    'no-clean'      : opts.no_clean,
578                    'dry-run'       : opts.dry_run,
579                    'jobs'          : 8 }
580
581        b = build(config, rtems_version(), prefix, tools, path.shell(opts.rtems), build_dir, options)
582        if opts.arch is not None:
583            if opts.bsp is not None:
584                b.build_arch_bsp(opts.arch, opts.bsp)
585            else:
586                b.build_arch(opts.arch)
587        else:
588            for profile in opts.profiles.split(','):
589                b.build_profile(profile.strip())
590
591    except error.general as gerr:
592        print(gerr)
593        print('BSP Build FAILED', file = sys.stderr)
594        ec = 1
595    except error.internal as ierr:
596        print(ierr)
597        print('BSP Build FAILED', file = sys.stderr)
598        ec = 1
599    except error.exit as eerr:
600        pass
601    except KeyboardInterrupt:
602        log.notice('abort: user terminated')
603        ec = 1
604    if b is not None:
605        b.results.report()
606    sys.exit(ec)
607
608def run():
609    run_args(sys.argv)
610
611if __name__ == "__main__":
612    run()
Note: See TracBrowser for help on using the repository browser.