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

Last change on this file since ef38b5d was ef38b5d, checked in by Chris Johns <chrisj@…>, on May 15, 2017 at 1:48:44 AM

rtemstoolkit: Add Python INI configuration support.

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