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

Last change on this file since ad15f6b was ad15f6b, checked in by Chris Johns <chrisj@…>, on Apr 27, 2017 at 8:49:23 PM

rtems-bsp-builder: Fix passes report alignment.

  • Property mode set to 100755
File size: 46.7 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2016-2017 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 re
38import sys
39import textwrap
40
41import pprint
42
43try:
44    import configparser
45except:
46    import ConfigParser as configparser
47
48from rtemstoolkit import execute
49from rtemstoolkit import error
50from rtemstoolkit import host
51from rtemstoolkit import log
52from rtemstoolkit import path
53from rtemstoolkit import textbox
54from rtemstoolkit import version
55
56def rtems_version():
57    return version.version()
58
59def wrap(line, lineend = '', indent = 0, width = 75):
60    if type(line) is tuple or type(line) is list:
61        if len(line) >= 2:
62            s1 = line[0]
63        else:
64            s1 = ''
65        s2 = line[1:]
66    elif type(line) is str:
67        s1 = ''
68        s2 = [line]
69    else:
70        raise error.internal('line is not a tuple, list or string')
71    s1len = len(s1)
72    s = ''
73    first = True
74    for ss in s2:
75        if type(ss) is not str and type(ss) is not unicode:
76            raise error.internal('text needs to be a string')
77        for l in textwrap.wrap(ss, width = width - s1len - indent - 1):
78            s += '%s%s%s%s%s' % (' ' * indent, s1, l, lineend, os.linesep)
79            if first and s1len > 0:
80                s1 = ' ' * s1len
81    if lineend != '':
82        s = s[:0 - len(os.linesep) - 1] + os.linesep
83    return s
84
85def title():
86    return 'RTEMS Tools Project - RTEMS Kernel BSP Builder, %s' % (version.str())
87
88def command_line():
89    return wrap(('command: ', ' '.join(sys.argv)), lineend = '\\')
90
91class warnings_errors:
92
93    def __init__(self, rtems):
94        self.rtems = path.host(rtems)
95        self.reset()
96        self.groups = { 'groups'  : ['Shared', 'BSP', 'Network', 'Tests',
97                                     'LibCPU', 'CPU Kit'],
98                        'exclude' : '.*Makefile.*',
99                        'CPU Kit' : '.*cpukit/.*',
100                        'Network' : '.*libnetworking/.*|.*librpc/.*',
101                        'Tests'   : '.*testsuites/.*',
102                        'BSP'     : '.*libbsp/.*',
103                        'LibCPU'  : '.*libcpu/.*',
104                        'Shared'  : '.*shared/.*' }
105        self.arch = None
106        self.bsp = None
107        self.build = None
108
109    def _opts(self, arch = None, bsp = None, build = None):
110        if arch is None:
111            arch = self.arch
112        if bsp is None:
113            bsp = self.bsp
114        if build is None:
115            build = self.build
116        return arch, bsp, build
117
118    def _key(self, arch, bsp, build):
119        arch, bsp, build = self._opts(arch, bsp, build)
120        return '%s/%s-%s' % (arch, bsp, build)
121
122    def _get_warnings(self, arch = None, bsp = None, build = None):
123        arch, bsp, build = self._opts(arch = arch, bsp = bsp, build = build)
124        if arch is None:
125            arch = '.*'
126        if bsp is None:
127            bsp = '.*'
128        if build is None:
129            build = '.*'
130        selector = re.compile('^%s/%s-%s$' % (arch, bsp, build))
131        warnings = [w for w in self.warnings if selector.match(w)]
132        return sorted(warnings)
133
134    def _total(self, archive):
135        total = 0
136        for a in archive:
137            total += archive[a]
138        return total
139
140    def _analyze(self, warnings, exclude):
141        def _group(data, category, name, warning, count, groups, group_regx):
142            if 'groups' not in data:
143                data['groups'] = { }
144            if category not in data['groups']:
145                data['groups'][category] = { 'totals' : { } }
146            if name not in data['groups'][category]:
147                data['groups'][category][name] = { }
148            for group in groups:
149                if group not in data['groups'][category]['totals']:
150                    data['groups'][category]['totals'][group] = 0
151                if group not in data['groups'][category][name]:
152                    data['groups'][category][name][group] = 0
153                if group_regx[group].match(warning):
154                    data['groups'][category][name][group] += count
155                    data['groups'][category]['totals'][group] += count
156                    break
157
158        def _update(data, category, name, warning, count, groups, group_regx):
159            if category not in data:
160                data[category] = { }
161            if name not in data[category]:
162                data[category][name] = { }
163            if warning not in data[category][name]:
164                data[category][name][warning] = 0
165            data[category][name][warning] += count
166            _group(data, category, name,  w, count, groups, group_regx)
167
168        data = { }
169        group_regx = { }
170        for group in self.groups['groups']:
171            group_regx[group] = re.compile(self.groups[group])
172        exclude_regx = re.compile(exclude)
173        for warning in warnings:
174            arch = warning.split('/', 1)[0]
175            arch_bsp = warning.split('-', 1)[0]
176            build = warning.split('-', 1)[1]
177            for w in self.warnings[warning]:
178                if not exclude_regx.match(w):
179                    count = self.warnings[warning][w]
180                    _update(data, 'arch',     arch,     w, count,
181                           self.groups['groups'], group_regx)
182                    _update(data, 'arch_bsp', arch_bsp, w, count,
183                           self.groups['groups'], group_regx)
184                    _update(data, 'build',  build,  w, count,
185                           self.groups['groups'], group_regx)
186        for category in ['arch', 'arch_bsp', 'build']:
187            common = {}
188            for name in data[category]:
189                for w in data[category][name]:
190                    if w not in common:
191                        for other in [n for n in data[category] if n != name]:
192                            if w in data[category][other]:
193                                common[w] = data[category][name][w]
194                                _group(data, category, 'common', w, common[w],
195                                       self.groups['groups'], group_regx)
196            data[category]['common'] = common
197        return data
198
199    def _report_category(self, label, warnings, group_counts):
200        width = 70
201        cols_1 = [width]
202        cols_2 = [8, width - 8]
203        cols_4 = textbox.even_columns(4, width)
204        cols_2_4 = textbox.merge_columns([cols_2, cols_4])
205        s = textbox.line(cols_1, line = '=', marker = '+', indent = 1)
206        s += textbox.row(cols_1, [' ' + label], indent = 1)
207        s += textbox.line(cols_1, marker = '+', indent = 1)
208        builds = ['common'] + sorted([b for b in warnings if b != 'common'])
209        common = warnings['common']
210        for build in builds:
211            build_warnings = warnings[build]
212            if build is not 'common':
213                build_warnings = [w for w in build_warnings if w not in common]
214            s += textbox.row(cols_1,
215                             [' %s : %d warning(s)' % (build,
216                                                       len(build_warnings))],
217                             indent = 1)
218            if len(build_warnings) == 0:
219                s += textbox.line(cols_1, marker = '+', indent = 1)
220            else:
221                s += textbox.line(cols_4, marker = '+', indent = 1)
222                if build not in group_counts:
223                    gs = [0 for group in self.groups['groups']]
224                else:
225                    gs = []
226                    for g in range(0, len(self.groups['groups'])):
227                        group = self.groups['groups'][g]
228                        if group in group_counts[build]:
229                            count = group_counts[build][group]
230                        else:
231                            count = 0
232                        gs += ['%*s' % (cols_4[g % 4] - 2,
233                                        '%s : %4d' % (group, count))]
234                    for row in range(0, len(self.groups['groups']), 4):
235                        if row + 4 > len(self.groups['groups']):
236                            d = gs[row:] + \
237                                ['' for r in range(row,
238                                                   len(self.groups['groups']))]
239                        else:
240                            d = gs[row:+4]
241                        s += textbox.row(cols_4, d, indent = 1)
242                s += textbox.line(cols_2_4, marker = '+', indent = 1)
243                vw = sorted([(w, warnings[build][w]) for w in build_warnings],
244                            key = operator.itemgetter(1),
245                            reverse = True)
246                for w in vw:
247                    c1 = '%6d' % w[1]
248                    for l in textwrap.wrap(' ' + w[0], width = cols_2[1] - 3):
249                        s += textbox.row(cols_2, [c1, l], indent = 1)
250                        c1 = ' ' * 6
251                s += textbox.line(cols_2, marker = '+', indent = 1)
252        return s
253
254    def _report_warning_map(self):
255        builds = self.messages['warnings']
256        width = 70
257        cols_1 = [width]
258        s = textbox.line(cols_1, line = '=', marker = '+', indent = 1)
259        s += textbox.row(cols_1, [' Warning Map'], indent = 1)
260        s += textbox.line(cols_1, marker = '+', indent = 1)
261        for build in builds:
262            messages = builds[build]
263            s += textbox.row(cols_1, [' %s : %d' % (build, len(messages))], indent = 1)
264            s += textbox.line(cols_1, marker = '+', indent = 1)
265            for msg in messages:
266                for l in textwrap.wrap(msg, width = width - 3):
267                    s += textbox.row(cols_1, [' ' + l], indent = 1)
268                for l in textwrap.wrap(messages[msg], width = width - 3 - 4):
269                    s += textbox.row(cols_1, ['    ' + l], indent = 1)
270            s += textbox.line(cols_1, marker = '+', indent = 1)
271        return s
272
273    def report(self):
274        arch, bsp, build = self._opts()
275        warnings = self._get_warnings(arch, bsp, build)
276        total = 0
277        for build in warnings:
278            total += self._total(self.warnings[build])
279        if total == 0:
280            s = ' No warnings'
281        else:
282            data = self._analyze(warnings, self.groups['exclude'])
283            s = self._report_category('By Architecture (total : %d)' % (total),
284                                      data['arch'], data['groups']['arch'])
285            s += os.linesep
286            s += self._report_category('By BSP (total : %d)' % (total),
287                                       data['arch_bsp'], data['groups']['arch_bsp'])
288            s += os.linesep
289            s += self._report_category('By Build (total : %d)' % (total),
290                                       data['build'], data['groups']['build'])
291            s += os.linesep
292            s += self._report_warning_map()
293            s += os.linesep
294
295        return s
296
297    def set_build(self, arch, bsp, build):
298        self.arch = arch
299        self.bsp = bsp
300        self.build = build
301        self.build_key = '%s/%s-%s' % (arch, bsp, build)
302        if self.build_key not in self.warnings:
303            self.warnings[self.build_key] = {}
304        if self.build_key not in self.errors:
305            self.errors[self.build_key] = {}
306
307    def clear_build(self):
308        self.arch = None
309        self.bsp = None
310        self.build = None
311        self.build_key = None
312
313    def get_warning_count(self):
314        return self.warning_count
315
316    def get_error_count(self):
317        return self.error_count
318
319    def reset(self):
320        self.warnings = { }
321        self.warning_count = 0
322        self.errors = { }
323        self.error_count = 0
324        self.messages = { 'warnings' : { }, 'errors' : { } }
325
326    def get_warning_messages(self, arch = None, bsp = None, build = None):
327        key = self._key(arch, bsp, build)
328        if key not in self.messages['warnings']:
329            return []
330        messages = self.messages['warnings'][key]
331        return ['%s %s' % (m, messages[m]) for m in messages]
332
333    def get_error_messages(self, arch = None, bsp = None, build = None):
334        key = self._key(arch, bsp, build)
335        if key not in self.messages['errors']:
336            return []
337        messages = self.messages['errors'][key]
338        return ['%s %s' % (m, messages[m]) for m in messages]
339
340    def output(self, text):
341        def _line_split(line, source_base):
342            ls = line.split(' ', 1)
343            fname = ls[0].split(':')
344            #
345            # Ignore compiler option warnings.
346            #
347            if len(fname) < 4:
348                return None
349            p = path.abspath(fname[0])
350            p = p.replace(source_base, '')
351            if path.isabspath(p):
352                p = p[1:]
353            return p, fname[1], fname[2], ls[1]
354
355        if self.build_key is not None and \
356           (' warning:' in text or ' error:' in text):
357            for l in text.splitlines():
358                if ' warning:' in l:
359                    self.warning_count += 1
360                    archive = self.warnings[self.build_key]
361                    messages = 'warnings'
362                elif ' error:' in l:
363                    self.error_count += 1
364                    archive = self.errors[self.build_key]
365                    messages = 'errors'
366                else:
367                    continue
368                line_parts = _line_split(l, self.rtems)
369                if line_parts is not None:
370                    src, line, pos, msg = line_parts
371                    where = '%s:%s:%s' % (src, line, pos)
372                    if where not in archive:
373                        archive[where] = 1
374                    else:
375                        archive[where] += 1
376                    if self.build_key not in self.messages[messages]:
377                        self.messages[messages][self.build_key] = { }
378                    self.messages[messages][self.build_key][where] = msg
379
380        log.output(text)
381
382class results:
383
384    def __init__(self):
385        self.passes = []
386        self.fails = []
387
388    def _arch_bsp(self, arch, bsp):
389        return '%s/%s' % (arch, bsp)
390
391    def add(self, good, arch, bsp, configure, warnings, error_messages):
392        if good:
393            self.passes += [(arch, bsp, configure, warnings, None)]
394        else:
395            self.fails += [(arch, bsp, configure, warnings, error_messages)]
396
397    def report(self):
398        log.notice('* Passes: %d   Failures: %d' %
399                   (len(self.passes), len(self.fails)))
400        log.output()
401        log.output('Build Report')
402        log.output('   Passes: %d   Failures: %d' %
403                   (len(self.passes), len(self.fails)))
404        log.output(' Failures:')
405        if len(self.fails) == 0:
406            log.output('  None')
407        else:
408            max_col = 0
409            for f in self.fails:
410                arch_bsp = self._arch_bsp(f[0], f[1])
411                if len(arch_bsp) > max_col:
412                    max_col = len(arch_bsp)
413            for f in self.fails:
414                config_cmd = f[2]
415                config_at = config_cmd.find('configure')
416                if config_at != -1:
417                    config_cmd = config_cmd[config_at:]
418                log.output(' %*s:' % (max_col + 2, self._arch_bsp(f[0], f[1])))
419                s1 = ' ' * 6
420                log.output(wrap([s1, config_cmd], lineend = '\\', width = 75))
421                if f[4] is not None:
422                    s1 = ' ' * len(s1)
423                    for msg in f[4]:
424                        log.output(wrap([s1, msg], lineend = '\\'))
425        log.output(' Passes:')
426        if len(self.passes) == 0:
427            log.output('  None')
428        else:
429            max_col = 0
430            for f in self.passes:
431                arch_bsp = self._arch_bsp(f[0], f[1])
432                if len(arch_bsp) > max_col:
433                    max_col = len(arch_bsp)
434            for f in self.passes:
435                config_cmd = f[2]
436                config_at = config_cmd.find('configure')
437                if config_at != -1:
438                    config_cmd = config_cmd[config_at:]
439                log.output(' %s (%5d):' % (self._arch_bsp(f[0], f[1]), f[3]))
440                log.output(wrap([' ' * 6, config_cmd], lineend = '\\', width = 75))
441
442class configuration:
443
444    def __init__(self):
445        self.config = configparser.ConfigParser()
446        self.name = None
447        self.archs = { }
448        self.builds_ = { }
449        self.profiles = { }
450        self.configurations = { }
451
452    def __str__(self):
453        import pprint
454        s = self.name + os.linesep
455        s += 'Archs:' + os.linesep + \
456             pprint.pformat(self.archs, indent = 1, width = 80) + os.linesep
457        s += 'Builds:' + os.linesep + \
458             pprint.pformat(self.builds_, indent = 1, width = 80) + os.linesep
459        s += 'Profiles:' + os.linesep + \
460             pprint.pformat(self.profiles, indent = 1, width = 80) + os.linesep
461        return s
462
463    def _get_item(self, section, label, err = True):
464        try:
465            rec = self.config.get(section, label).replace(os.linesep, ' ')
466            return rec
467        except:
468            if err:
469                raise error.general('config: no "%s" found in "%s"' % (label, section))
470        return None
471
472    def _get_items(self, section, err = True):
473        try:
474            items = [(name, key.replace(os.linesep, ' ')) \
475                     for name, key in self.config.items(section)]
476            return items
477        except:
478            if err:
479                raise error.general('config: section "%s" not found' % (section))
480        return []
481
482    def _comma_list(self, section, label, error = True):
483        items = self._get_item(section, label, error)
484        if items is None:
485            return []
486        return sorted(set([a.strip() for a in items.split(',')]))
487
488    def _get_item_names(self, section, err = True):
489        try:
490            return [item[0] for item in self.config.items(section)]
491        except:
492            if err:
493                raise error.general('config: section "%s" not found' % (section))
494        return []
495
496    def _build_options(self, build, nesting = 0):
497        if ':' in build:
498            section, name = build.split(':', 1)
499            opts = [self._get_item(section, name)]
500            return opts
501        builds = self.builds_['builds']
502        if build not in builds:
503            raise error.general('build %s not found' % (build))
504        if nesting > 20:
505            raise error.general('nesting build %s' % (build))
506        options = []
507        for option in self.builds_['builds'][build]:
508            if ':' in option:
509                section, name = option.split(':', 1)
510                opts = [self._get_item(section, name)]
511            else:
512                opts = self._options(option, nesting + 1)
513            for opt in opts:
514                if opt not in options:
515                    options += [opt]
516        return options
517
518    def load(self, name, build):
519        if not path.exists(name):
520            raise error.general('config: cannot read configuration: %s' % (name))
521        self.name = name
522        try:
523            self.config.read(name)
524        except configparser.ParsingError as ce:
525            raise error.general('config: %s' % (ce))
526        archs = []
527        self.profiles['profiles'] = self._comma_list('profiles', 'profiles', error = False)
528        if len(self.profiles['profiles']) == 0:
529            self.profiles['profiles'] = ['tier-%d' % (t) for t in range(1,4)]
530        for p in self.profiles['profiles']:
531            profile = {}
532            profile['name'] = p
533            profile['archs'] = self._comma_list(profile['name'], 'archs')
534            archs += profile['archs']
535            for arch in profile['archs']:
536                bsps = 'bsps_%s' % (arch)
537                profile[bsps] = self._comma_list(profile['name'], bsps)
538            self.profiles[profile['name']] = profile
539        for a in set(archs):
540            arch = {}
541            arch['excludes'] = {}
542            for exclude in self._comma_list(a, 'exclude', error = False):
543                arch['excludes'][exclude] = ['all']
544            for i in self._get_items(a, False):
545                if i[0].startswith('exclude_'):
546                    exclude = i[0][len('exclude_'):]
547                    if exclude not in arch['excludes']:
548                        arch['excludes'][exclude] = []
549                    arch['excludes'][exclude] += sorted(set([b.strip() for b in i[1].split(',')]))
550            arch['bsps'] = self._comma_list(a, 'bsps', error = False)
551            for b in arch['bsps']:
552                arch[b] = {}
553                arch[b]['bspopts'] = self._comma_list(a, 'bspopts_%s' % (b), error = False)
554            self.archs[a] = arch
555        builds = {}
556        builds['default'] = self._get_item('builds', 'default')
557        if build is None:
558            build = builds['default']
559        builds['config'] = { }
560        for config in self._get_items('config'):
561            builds['config'][config[0]] = config[1]
562        builds['build'] = build
563        builds_ = self._get_item_names('builds')
564        builds['builds'] = {}
565        for build in builds_:
566            build_builds = self._comma_list('builds', build)
567            has_config = False
568            has_build = False
569            for b in build_builds:
570                if ':' in b:
571                    if has_build:
572                        raise error.general('config and build in build: %s' % (build))
573                    has_config = True
574                else:
575                    if has_config:
576                        raise error.general('config and build in build: %s' % (build))
577                    has_build = True
578            builds['builds'][build] = build_builds
579        self.builds_ = builds
580
581    def build(self):
582        return self.builds_['build']
583
584    def builds(self):
585        if self.builds_['build'] in self.builds_['builds']:
586            build = self.builds_['builds'][self.builds_['build']]
587            if ':' in build[0]:
588                return [self.builds_['build']]
589            return build
590        return None
591
592    def build_options(self, build):
593        return ' '.join(self._build_options(build))
594
595    def excludes(self, arch):
596        excludes = self.archs[arch]['excludes'].keys()
597        for exclude in self.archs[arch]['excludes']:
598            if 'all' not in self.archs[arch]['excludes'][exclude]:
599                excludes.remove(exclude)
600        return sorted(excludes)
601
602    def archs(self):
603        return sorted(self.archs.keys())
604
605    def arch_present(self, arch):
606        return arch in self.archs
607
608    def arch_bsps(self, arch):
609        return sorted(self.archs[arch]['bsps'])
610
611    def bsp_present(self, arch, bsp):
612        return bsp in self.archs[arch]['bsps']
613
614    def bsp_excludes(self, arch, bsp):
615        excludes = self.archs[arch]['excludes'].keys()
616        for exclude in self.archs[arch]['excludes']:
617            if bsp not in self.archs[arch]['excludes'][exclude]:
618                excludes.remove(exclude)
619        return sorted(excludes)
620
621    def bspopts(self, arch, bsp):
622        return self.archs[arch][bsp]['bspopts']
623
624    def profile_present(self, profile):
625        return profile in self.profiles
626
627    def profile_archs(self, profile):
628        return self.profiles[profile]['archs']
629
630    def profile_arch_bsps(self, profile, arch):
631        return self.profiles[profile]['bsps_%s' % (arch)]
632
633    def report(self, profiles = True, builds = True, architectures = True):
634        width = 70
635        cols_1 = [width]
636        cols_2 = [10, width - 10]
637        s = textbox.line(cols_1, line = '=', marker = '+', indent = 1)
638        s1 = ' File'
639        colon = ':'
640        for l in textwrap.wrap(self.name, width = cols_2[1] - 3):
641            s += textbox.row(cols_2, [s1, ' ' + l], marker = colon, indent = 1)
642            colon = ' '
643            s1 = ' ' * len(s1)
644        s += textbox.line(cols_1, marker = '+', indent = 1)
645        s += textbox.line(cols_1, line = '=', marker = '+', indent = 1)
646        if profiles:
647            profiles = sorted(self.profiles['profiles'])
648            bsps = 0
649            for profile in profiles:
650                archs = sorted(self.profiles[profile]['archs'])
651                for arch in archs:
652                    bsps += len(self.profiles[profile]['bsps_%s' % (arch)])
653            s += textbox.row(cols_1,
654                             [' Profiles : %d/%d' % (len(archs), bsps)],
655                             indent = 1)
656            for profile in profiles:
657                textbox.row(cols_2,
658                            [profile, self.profiles[profile]['name']],
659                            indent = 1)
660            s += textbox.line(cols_1, marker = '+', indent = 1)
661            for profile in profiles:
662                s += textbox.row(cols_1, [' %s' % (profile)], indent = 1)
663                profile = self.profiles[profile]
664                archs = sorted(profile['archs'])
665                for arch in archs:
666                    s += textbox.line(cols_2, marker = '+', indent = 1)
667                    s1 = ' ' + arch
668                    for l in textwrap.wrap(', '.join(profile['bsps_%s' % (arch)]),
669                                           width = cols_2[1] - 2):
670                        s += textbox.row(cols_2, [s1, ' ' + l], indent = 1)
671                        s1 = ' ' * len(s1)
672                s += textbox.line(cols_2, marker = '+', indent = 1)
673        if builds:
674            s += textbox.line(cols_1, line = '=', marker = '+', indent = 1)
675            s += textbox.row(cols_1,
676                             [' Builds:  %s (default)' % (self.builds_['default'])],
677                             indent = 1)
678            builds = self.builds_['builds']
679            bsize = 0
680            for build in builds:
681                if len(build) > bsize:
682                    bsize = len(build)
683            cols_b = [bsize + 2, width - bsize - 2]
684            s += textbox.line(cols_b, marker = '+', indent = 1)
685            for build in builds:
686                s1 = ' ' + build
687                for l in textwrap.wrap(', '.join(builds[build]),
688                                       width = cols_b[1] - 2):
689                    s += textbox.row(cols_b, [s1, ' ' + l], indent = 1)
690                    s1 = ' ' * len(s1)
691                s += textbox.line(cols_b, marker = '+', indent = 1)
692            configs = self.builds_['config']
693            s += textbox.row(cols_1,
694                             [' Configure Options: %d' % (len(configs))],
695                             indent = 1)
696            csize = 0
697            for config in configs:
698                if len(config) > csize:
699                    csize = len(config)
700            cols_c = [csize + 3, width - csize - 3]
701            s += textbox.line(cols_c, marker = '+', indent = 1)
702            for config in configs:
703                s1 = ' ' + config
704                for l in textwrap.wrap(configs[config], width = cols_c[1] - 3):
705                    s += textbox.row(cols_c, [s1, ' ' + l], indent = 1)
706                    s1 = ' ' * len(s1)
707                s += textbox.line(cols_c, marker = '+', indent = 1)
708        if architectures:
709            s += textbox.line(cols_1, line = '=', marker = '+', indent = 1)
710            archs = sorted(self.archs.keys())
711            bsps = 0
712            asize = 0
713            for arch in archs:
714                if len(arch) > asize:
715                    asize = len(arch)
716                bsps += len(self.archs[arch]['bsps'])
717            s += textbox.row(cols_1,
718                             [' Architectures : %d (bsps: %d)' % (len(archs), bsps)],
719                             indent = 1)
720            cols_a = [asize + 2, width - asize - 2]
721            s += textbox.line(cols_a, marker = '+', indent = 1)
722            for arch in archs:
723                s += textbox.row(cols_a,
724                                 [' ' + arch, ' %d' % (len(self.archs[arch]['bsps']))],
725                                 indent = 1)
726            s += textbox.line(cols_a, marker = '+', indent = 1)
727            for archn in archs:
728                arch = self.archs[archn]
729                if len(arch['bsps']) > 0:
730                    bsize = 0
731                    for bsp in arch['bsps']:
732                        if len(bsp) > bsize:
733                            bsize = len(bsp)
734                    cols_b = [bsize + 3, width - bsize - 3]
735                    s += textbox.row(cols_1, [' ' + archn + ':'], indent = 1)
736                    s += textbox.line(cols_b, marker = '+', indent = 1)
737                    for bsp in arch['bsps']:
738                        s1 = ' ' + bsp
739                        bspopts = ' '.join(arch[bsp]['bspopts'])
740                        if len(bspopts):
741                            for l in textwrap.wrap('bopt: ' + bspopts,
742                                                   width = cols_b[1] - 3):
743                                s += textbox.row(cols_b, [s1, ' ' + l], indent = 1)
744                                s1 = ' ' * len(s1)
745                        excludes = []
746                        for exclude in arch['excludes']:
747                            if bsp in arch['excludes'][exclude]:
748                                excludes += [exclude]
749                        excludes = ', '.join(excludes)
750                        if len(excludes):
751                            for l in textwrap.wrap('ex: ' + excludes,
752                                                   width = cols_b[1] - 3):
753                                s += textbox.row(cols_b, [s1, ' ' + l], indent = 1)
754                                s1 = ' ' * len(s1)
755                        if len(bspopts) == 0 and len(excludes) == 0:
756                            s += textbox.row(cols_b, [s1, ' '], indent = 1)
757                    s += textbox.line(cols_b, marker = '+', indent = 1)
758        return s
759
760class build:
761
762    def __init__(self, config, version, prefix, tools, rtems, build_dir, options):
763        self.config = config
764        self.build_dir = build_dir
765        self.rtems_version = version
766        self.prefix = prefix
767        self.tools = tools
768        self.rtems = rtems
769        self.options = options
770        self.errors = { 'configure': 0,
771                        'build':     0,
772                        'tests':     0,
773                        'fails':     []}
774        self.counts = { 'h'        : 0,
775                        'exes'     : 0,
776                        'objs'     : 0,
777                        'libs'     : 0 }
778        self.warnings_errors = warnings_errors(rtems)
779        self.results = results()
780        if not path.exists(path.join(rtems, 'configure')) or \
781           not path.exists(path.join(rtems, 'Makefile.in')) or \
782           not path.exists(path.join(rtems, 'cpukit')):
783            raise error.general('RTEMS source path does not look like RTEMS')
784
785    def _error_str(self):
786        return 'Status: configure:%d build:%d' % \
787            (self.errors['configure'], self.errors['build'])
788
789    def _path(self, arch, bsp):
790        return path.join(self.build_dir, arch, bsp)
791
792    def _archs(self, build_data):
793        return sorted(build_data.keys())
794
795    def _bsps(self, arch):
796        return self.config.arch_bsps(arch)
797
798    def _build(self):
799        return self.config.build()
800
801    def _builds(self, arch, bsp):
802        builds = self.config.builds()
803        if builds is None:
804            return None
805        for b in self.config.excludes(arch):
806            if b in builds:
807                builds.remove(b)
808        for b in self.config.bsp_excludes(arch, bsp):
809            if b in builds:
810                builds.remove(b)
811        return builds
812
813    def _arch_bsp_dir_make(self, arch, bsp):
814        if not path.exists(self._path(arch, bsp)):
815            path.mkdir(self._path(arch, bsp))
816
817    def _arch_bsp_dir_clean(self, arch, bsp):
818        if path.exists(self._path(arch, bsp)):
819            path.removeall(self._path(arch, bsp))
820
821    def _config_command(self, commands, arch, bsp):
822        if type(commands) is not list:
823            commands = [commands]
824        cmd = [path.join(self.rtems, 'configure')]
825        commands += self.config.bspopts(arch, bsp)
826        for c in commands:
827            c = c.replace('@PREFIX@', self.prefix)
828            c = c.replace('@RTEMS_VERSION@', self.rtems_version)
829            c = c.replace('@ARCH@', arch)
830            c = c.replace('@BSP@', bsp)
831            cmd += [c]
832        return ' '.join(cmd)
833
834    def _build_set(self, builds):
835        build_set = { }
836        for build in builds:
837            build_set[build] = self.config.build_options(build)
838        return build_set
839
840    def _build_dir(self, arch, bsp, build):
841        return path.join(self._path(arch, bsp), build)
842
843    def _count_files(self, arch, bsp, build):
844        counts = { 'h'    : 0,
845                   'exes' : 0,
846                   'objs' : 0,
847                   'libs' : 0 }
848        for root, dirs, files in os.walk(self._build_dir(arch, bsp, build)):
849            for file in files:
850                if file.endswith('.exe'):
851                    counts['exes'] += 1
852                elif file.endswith('.o'):
853                    counts['objs'] += 1
854                elif file.endswith('.a'):
855                    counts['libs'] += 1
856                elif file.endswith('.h'):
857                    counts['h'] += 1
858        for f in self.counts:
859            if f in counts:
860                self.counts[f] += counts[f]
861        return counts
862
863    def _have_failures(self, fails):
864        return len(fails) != 0
865
866    def _warnings_report(self):
867        if self.options['warnings-report'] is not None:
868            with open(self.options['warnings-report'], 'w') as f:
869                f.write(title() + os.linesep)
870                f.write(os.linesep)
871                f.write('Date: %s%s' % (datetime.datetime.now().strftime('%c'),
872                                        os.linesep))
873                f.write(os.linesep)
874                f.write(command_line() + os.linesep)
875                f.write(self.warnings_errors.report())
876
877    def _finished(self):
878        log.notice('+  warnings:%d  exes:%d  objs:%d  libs:%d' % \
879                   (self.warnings_errors.get_warning_count(), self.counts['exes'],
880                    self.counts['objs'], self.counts['libs']))
881        log.output()
882        log.output('Warnings:')
883        log.output(self.warnings_errors.report())
884        log.output()
885        log.notice('Failures:')
886        log.notice(self.failures_report(self.errors['fails']))
887        self._warnings_report()
888
889    def failures_report(self, fails):
890        if not self._have_failures(fails):
891            return ' No failure(s)'
892        absize = 0
893        bsize = 0
894        ssize = 0
895        for f in fails:
896            arch_bsp = '%s/%s' % (f[1], f[2])
897            if len(arch_bsp) > absize:
898                absize = len(arch_bsp)
899            if len(f[3]) > bsize:
900                bsize = len(f[3])
901            if len(f[0]) > ssize:
902                ssize = len(f[0])
903        fc = 1
904        s = ''
905        for f in fails:
906            fcl = ' %3d' % (fc)
907            arch_bsp = '%s/%s' % (f[1], f[2])
908            state = f[0]
909            s += '%s %-*s %-*s %-*s:%s' % \
910                 (fcl, bsize, f[3], absize, arch_bsp, ssize, state, os.linesep)
911            s1 = ' ' * 6
912            s += wrap((s1, 'configure: ' + f[4]), lineend = '\\', width = 75)
913            for e in self.warnings_errors.get_error_messages(f[1], f[2], f[3]):
914                s += wrap([s1, 'error: ' + e])
915            fc += 1
916        return s
917
918    def build_arch_bsp(self, arch, bsp):
919        if not self.config.bsp_present(arch, bsp):
920            raise error.general('BSP not found: %s/%s' % (arch, bsp))
921        log.output('-' * 70)
922        log.notice('] BSP: %s/%s' % (arch, bsp))
923        log.notice('. Creating: %s' % (self._path(arch, bsp)))
924        self._arch_bsp_dir_clean(arch, bsp)
925        self._arch_bsp_dir_make(arch, bsp)
926        builds = self._builds(arch, bsp)
927        if builds is None:
928            raise error.general('build not found: %s' % (self._build()))
929        build_set = self._build_set(builds)
930        bsp_start = datetime.datetime.now()
931        env_path = os.environ['PATH']
932        os.environ['PATH'] = path.host(path.join(self.tools, 'bin')) + \
933                             os.pathsep + os.environ['PATH']
934        fails = []
935        for bs in sorted(build_set.keys()):
936            self.warnings_errors.set_build(arch, bsp, bs)
937            start = datetime.datetime.now()
938            log.output('- ' * 35)
939            log.notice('. Configuring: %s' % (bs))
940            try:
941                warnings = self.warnings_errors.get_warning_count()
942                result = '+ Pass'
943                bpath = self._build_dir(arch, bsp, bs)
944                good = True
945                error_messages = None
946                path.mkdir(bpath)
947                config_cmd = self._config_command(build_set[bs], arch, bsp)
948                cmd = config_cmd
949                e = execute.capture_execution(log = self.warnings_errors)
950                log.output(wrap(('run: ', cmd), lineend = '\\'))
951                if self.options['dry-run']:
952                    exit_code = 0
953                else:
954                    exit_code, proc, output = e.shell(cmd, cwd = path.host(bpath))
955                if exit_code != 0:
956                    result = '- FAIL'
957                    failure = ('configure', arch, bsp, bs, config_cmd)
958                    fails += [failure]
959                    self.errors['configure'] += 1
960                    self.errors['fails'] += [failure]
961                    log.notice('- Configure failed: %s' % (bs))
962                    log.output('cmd failed: %s' % (cmd))
963                    good = False
964                else:
965                    log.notice('. Building: %s' % (bs))
966                    cmd = 'make'
967                    if 'jobs' in self.options:
968                        cmd += ' -j %s' % (self.options['jobs'])
969                    log.output('run: ' + cmd)
970                    if self.options['dry-run']:
971                        exit_code = 0
972                    else:
973                        exit_code, proc, output = e.shell(cmd, cwd = path.host(bpath))
974                    if exit_code != 0:
975                        error_messages = self.warnings_errors.get_error_messages()
976                        result = '- FAIL'
977                        failure = ('build', arch, bsp, bs, config_cmd, error_messages)
978                        fails += [failure]
979                        self.errors['build'] += 1
980                        self.errors['fails'] += [failure]
981                        log.notice('- FAIL: %s: %s' % (bs, self._error_str()))
982                        log.output('cmd failed: %s' % (cmd))
983                        good = False
984                    files = self._count_files(arch, bsp, bs)
985                    log.notice('%s: %s: warnings:%d  exes:%d  objs:%s  libs:%d' % \
986                               (result, bs,
987                                self.warnings_errors.get_warning_count() - warnings,
988                                files['exes'], files['objs'], files['libs']))
989                log.notice(%s' % (self._error_str()))
990                self.results.add(good, arch, bsp, config_cmd,
991                                 self.warnings_errors.get_warning_count() - warnings,
992                                 error_messages)
993                if not good and self.options['stop-on-error']:
994                    raise error.general('Configuring %s failed' % (bs))
995            finally:
996                end = datetime.datetime.now()
997                if not self.options['no-clean']:
998                    log.notice('. Cleaning: %s' % (self._build_dir(arch, bsp, bs)))
999                    path.removeall(self._build_dir(arch, bsp, bs))
1000            log.notice('^ Time %s' % (str(end - start)))
1001            self.warnings_errors.clear_build()
1002        bsp_end = datetime.datetime.now()
1003        log.notice('^ BSP Time %s' % (str(bsp_end - bsp_start)))
1004        log.output('Failure Report:')
1005        log.output(self.failures_report(fails))
1006        os.environ['PATH'] = env_path
1007
1008    def build_bsp(self, arch, bsp):
1009        self.build_arch_bsp(arch, bsp)
1010        self._finished()
1011
1012    def build_arch(self, arch):
1013        start = datetime.datetime.now()
1014        log.output('=' * 70)
1015        log.notice(']] Architecture: %s' % (arch))
1016        if not self.config.arch_present(arch):
1017            raise error.general('Architecture not found: %s' % (arch))
1018        for bsp in self._bsps(arch):
1019            self.build_arch_bsp(arch, bsp)
1020        end = datetime.datetime.now()
1021        log.notice('^ Architecture Time %s' % (str(end - start)))
1022        self._finished()
1023
1024    def build(self):
1025        for arch in self.config.archs():
1026            self.build_arch(arch)
1027        log.notice('^ Profile Time %s' % (str(end - start)))
1028        self._finished()
1029
1030    def build_profile(self, profile):
1031        if not self.config.profile_present(profile):
1032            raise error.general('Profile not found: %s' % (profile))
1033        start = datetime.datetime.now()
1034        log.notice(']] Profile: %s' % (profile))
1035        for arch in self.config.profile_archs(profile):
1036            for bsp in self.config.profile_arch_bsps(profile, arch):
1037                self.build_arch_bsp(arch, bsp)
1038        end = datetime.datetime.now()
1039        log.notice('^ Profile Time %s' % (str(end - start)))
1040        self._finished()
1041
1042def run_args(args):
1043    b = None
1044    ec = 0
1045    try:
1046        #
1047        # On Windows MSYS2 prepends a path to itself to the environment
1048        # path. This means the RTEMS specific automake is not found and which
1049        # breaks the bootstrap. We need to remove the prepended path. Also
1050        # remove any ACLOCAL paths from the environment.
1051        #
1052        if os.name == 'nt':
1053            cspath = os.environ['PATH'].split(os.pathsep)
1054            if 'msys' in cspath[0] and cspath[0].endswith('bin'):
1055                os.environ['PATH'] = os.pathsep.join(cspath[1:])
1056
1057        top = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
1058        prefix = '/opt/rtems/%s' % (rtems_version())
1059        tools = prefix
1060        build_dir = 'bsp-builds'
1061        logf = 'bsp-build-%s.txt' % \
1062               (datetime.datetime.now().strftime('%Y%m%d-%H%M%S'))
1063        config_file = path.join(top, 'share', 'rtems', 'tester',
1064                                'rtems', 'rtems-bsps.ini')
1065        if not path.exists(config_file):
1066            config_file = path.join(top, 'tester', 'rtems', 'rtems-bsps.ini')
1067
1068        argsp = argparse.ArgumentParser()
1069        argsp.add_argument('--prefix', help = 'Prefix to build the BSP.',
1070                           type = str)
1071        argsp.add_argument('--rtems-tools', help = 'The RTEMS tools directory.',
1072                           type = str)
1073        argsp.add_argument('--rtems', help = 'The RTEMS source tree.',
1074                           type = str)
1075        argsp.add_argument('--config-report', help = 'Report the configuration.',
1076                           action = 'store_true')
1077        argsp.add_argument('--warnings-report', help = 'Report the warnings to a file.',
1078                           type = str, default = None)
1079        argsp.add_argument('--build-path', help = 'Path to build in.',
1080                           type = str)
1081        argsp.add_argument('--log', help = 'Log file.', type = str)
1082        argsp.add_argument('--stop-on-error', help = 'Stop on an error.',
1083                           action = 'store_true')
1084        argsp.add_argument('--no-clean', help = 'Do not clean the build output.',
1085                           action = 'store_true')
1086        argsp.add_argument('--profiles', help = 'Build the listed profiles.',
1087                           type = str, default = 'tier-1')
1088        argsp.add_argument('--build', help = 'Build name to build.',
1089                           type = str, default='all')
1090        argsp.add_argument('--arch', help = 'Build the specific architecture.',
1091                           type = str)
1092        argsp.add_argument('--bsp', help = 'Build the specific BSP.',
1093                           type = str)
1094        argsp.add_argument('--jobs', help = 'Number of jobs to run.',
1095                           type = int, default = host.cpus())
1096        argsp.add_argument('--dry-run', help = 'Do not run the actual builds.',
1097                           action = 'store_true')
1098
1099        opts = argsp.parse_args(args[1:])
1100        if opts.log is not None:
1101            logf = opts.log
1102        log.default = log.log([logf])
1103        log.notice(title())
1104        log.output(command_line())
1105        if opts.rtems is None:
1106            raise error.general('No RTEMS source provided on the command line')
1107        if opts.prefix is not None:
1108            prefix = path.shell(opts.prefix)
1109        if opts.rtems_tools is not None:
1110            tools = path.shell(opts.rtems_tools)
1111        if opts.build_path is not None:
1112            build_dir = path.shell(opts.build_path)
1113        if opts.bsp is not None and opts.arch is None:
1114            raise error.general('BSP provided but no architecture')
1115
1116        config = configuration()
1117        config.load(config_file, opts.build)
1118
1119        if opts.config_report:
1120            log.notice('Configuration Report:')
1121            log.notice(config.report())
1122            sys.exit(0)
1123
1124        options = { 'stop-on-error'   : opts.stop_on_error,
1125                    'no-clean'        : opts.no_clean,
1126                    'dry-run'         : opts.dry_run,
1127                    'jobs'            : opts.jobs,
1128                    'warnings-report' : opts.warnings_report }
1129
1130        b = build(config, rtems_version(), prefix, tools,
1131                  path.shell(opts.rtems), build_dir, options)
1132        if opts.arch is not None:
1133            if opts.bsp is not None:
1134                b.build_bsp(opts.arch, opts.bsp)
1135            else:
1136                b.build_arch(opts.arch)
1137        else:
1138            for profile in opts.profiles.split(','):
1139                b.build_profile(profile.strip())
1140
1141    except error.general as gerr:
1142        print(gerr)
1143        print('BSP Build FAILED', file = sys.stderr)
1144        ec = 1
1145    except error.internal as ierr:
1146        print(ierr)
1147        print('BSP Build FAILED', file = sys.stderr)
1148        ec = 1
1149    except error.exit as eerr:
1150        pass
1151    except KeyboardInterrupt:
1152        log.notice('abort: user terminated')
1153        ec = 1
1154    if b is not None:
1155        b.results.report()
1156    sys.exit(ec)
1157
1158def run():
1159    run_args(sys.argv)
1160
1161if __name__ == "__main__":
1162    run()
Note: See TracBrowser for help on using the repository browser.