diff options
-rwxr-xr-x | tester/rt/check.py | 819 | ||||
-rw-r--r-- | tester/rtems/rtems-bsps.ini | 71 |
2 files changed, 716 insertions, 174 deletions
diff --git a/tester/rt/check.py b/tester/rt/check.py index 7e1fc3f..d33d606 100755 --- a/tester/rt/check.py +++ b/tester/rt/check.py @@ -1,6 +1,6 @@ # # RTEMS Tools Project (http://www.rtems.org/) -# Copyright 2016 Chris Johns (chrisj@rtems.org) +# Copyright 2016-2017 Chris Johns (chrisj@rtems.org) # All rights reserved. # # This file is part of the RTEMS Tools package in 'rtems-tools'. @@ -34,7 +34,11 @@ import argparse import datetime import operator import os +import re import sys +import textwrap + +import pprint try: import configparser @@ -43,60 +47,327 @@ except: from rtemstoolkit import execute from rtemstoolkit import error +from rtemstoolkit import host from rtemstoolkit import log from rtemstoolkit import path +from rtemstoolkit import textbox from rtemstoolkit import version def rtems_version(): return version.version() -class warnings_counter: +def wrap(line, lineend = '', indent = 0): + if type(line) is tuple or type(line) is list: + if len(line) >= 2: + s1 = line[0] + else: + s1 = '' + s2 = line[1:] + elif type(line) is str: + s1 = '' + s2 = [line] + else: + raise error.internal('line is not a tuple, list or string') + s = '' + first = True + for ss in s2: + if type(ss) is not str and type(ss) is not unicode: + raise error.internal('text needs to be a string') + for l in textwrap.wrap(ss): + s += '%s%s%s%s%s' % (' ' * indent, s1, l, lineend, os.linesep) + if first and len(s1) > 0: + s1 = ' ' * len(s1) + if lineend != '': + s = s[:0 - len(os.linesep) - 1] + os.linesep + return s + +def title(): + return 'RTEMS Tools Project - RTEMS Kernel BSP Builder, %s' % (version.str()) + +def command_line(): + return wrap(('command: ', ' '.join(sys.argv)), lineend = '\\') + +class warnings_errors: def __init__(self, rtems): self.rtems = path.host(rtems) self.reset() + self.groups = { 'groups' : ['Shared', 'BSP', 'Network', 'Tests', + 'LibCPU', 'CPU Kit'], + 'exclude' : '.*Makefile.*', + 'CPU Kit' : '.*cpukit/.*', + 'Network' : '.*libnetworking/.*', + 'Tests' : '.*testsuites/.*', + 'BSP' : '.*libbsp/.*', + 'LibCPU' : '.*libcpu/.*', + 'Shared' : '.*shared/.*' } + self.arch = None + self.bsp = None + self.build = None + + def _opts(self, arch = None, bsp = None, build = None): + if arch is None: + arch = self.arch + if bsp is None: + bsp = self.bsp + if build is None: + build = self.build + return arch, bsp, build + + def _key(self, arch, bsp, build): + arch, bsp, build = self._opts(arch, bsp, build) + return '%s/%s-%s' % (arch, bsp, build) + + def _get_warnings(self, arch = None, bsp = None, build = None): + arch, bsp, build = self._opts(arch = arch, bsp = bsp, build = build) + if arch is None: + arch = '.*' + if bsp is None: + bsp = '.*' + if build is None: + build = '.*' + selector = re.compile('^%s/%s-%s$' % (arch, bsp, build)) + warnings = [w for w in self.warnings if selector.match(w)] + return sorted(warnings) + + def _total(self, archive): + total = 0 + for a in archive: + total += archive[a] + return total + + def _analyze(self, warnings, exclude): + def _group(data, category, name, warning, count, groups, group_regx): + if 'groups' not in data: + data['groups'] = { } + if category not in data['groups']: + data['groups'][category] = { 'totals' : { } } + if name not in data['groups'][category]: + data['groups'][category][name] = { } + for group in groups: + if group not in data['groups'][category]['totals']: + data['groups'][category]['totals'][group] = 0 + if group not in data['groups'][category][name]: + data['groups'][category][name][group] = 0 + if group_regx[group].match(warning): + data['groups'][category][name][group] += count + data['groups'][category]['totals'][group] += count + break + + def _update(data, category, name, warning, count, groups, group_regx): + if category not in data: + data[category] = { } + if name not in data[category]: + data[category][name] = { } + if warning not in data[category][name]: + data[category][name][warning] = 0 + data[category][name][warning] += count + _group(data, category, name, w, count, groups, group_regx) + + data = { } + group_regx = { } + for group in self.groups['groups']: + group_regx[group] = re.compile(self.groups[group]) + exclude_regx = re.compile(exclude) + for warning in warnings: + arch = warning.split('/', 1)[0] + arch_bsp = warning.split('-', 1)[0] + build = warning.split('-', 1)[1] + for w in self.warnings[warning]: + if not exclude_regx.match(w): + count = self.warnings[warning][w] + _update(data, 'arch', arch, w, count, + self.groups['groups'], group_regx) + _update(data, 'arch_bsp', arch_bsp, w, count, + self.groups['groups'], group_regx) + _update(data, 'build', build, w, count, + self.groups['groups'], group_regx) + for category in ['arch', 'arch_bsp', 'build']: + common = {} + for name in data[category]: + for w in data[category][name]: + if w not in common: + for other in [n for n in data[category] if n != name]: + if w in data[category][other]: + common[w] = data[category][name][w] + _group(data, category, 'common', w, common[w], + self.groups['groups'], group_regx) + data[category]['common'] = common + return data + + def _report_category(self, label, warnings, group_counts): + width = 70 + cols_1 = [width] + cols_2 = [8, width - 8] + cols_4 = textbox.even_columns(4, width) + cols_2_4 = textbox.merge_columns([cols_2, cols_4]) + s = textbox.line(cols_1, line = '=', marker = '+', indent = 1) + s += textbox.row(cols_1, [' ' + label], indent = 1) + s += textbox.line(cols_1, marker = '+', indent = 1) + builds = ['common'] + sorted([b for b in warnings if b != 'common']) + common = warnings['common'] + for build in builds: + build_warnings = warnings[build] + if build is not 'common': + build_warnings = [w for w in build_warnings if w not in common] + s += textbox.row(cols_1, + [' %s : %d warning(s)' % (build, + len(build_warnings))], + indent = 1) + if len(build_warnings) == 0: + s += textbox.line(cols_1, marker = '+', indent = 1) + else: + s += textbox.line(cols_4, marker = '+', indent = 1) + if build not in group_counts: + gs = [0 for group in self.groups['groups']] + else: + gs = [] + for g in range(0, len(self.groups['groups'])): + group = self.groups['groups'][g] + gs += ['%*s' % (cols_4[g % 4] - 2, + '%s : %4d' % \ + (group, + group_counts[build][group]))] + for row in range(0, len(self.groups['groups']), 4): + if row + 4 > len(self.groups['groups']): + d = gs[row:] + \ + ['' for r in range(row, + len(self.groups['groups']))] + else: + d = gs[row:+4] + s += textbox.row(cols_4, d, indent = 1) + s += textbox.line(cols_2_4, marker = '+', indent = 1) + vw = sorted([(w, warnings[build][w]) for w in build_warnings], + key = operator.itemgetter(1), + reverse = True) + for w in vw: + c1 = '%6d' % w[1] + for l in textwrap.wrap(' ' + w[0]): + s += textbox.row(cols_2, [c1, l], indent = 1) + c1 = ' ' * 6 + s += textbox.line(cols_2, marker = '+', indent = 1) + return s + + def _report_warning_map(self): + builds = self.messages['warnings'] + width = 70 + cols_1 = [width] + s = textbox.line(cols_1, line = '=', marker = '+', indent = 1) + s += textbox.row(cols_1, [' Warning Map'], indent = 1) + s += textbox.line(cols_1, marker = '+', indent = 1) + for build in builds: + messages = builds[build] + s += textbox.row(cols_1, [' %s : %d' % (build, len(messages))], indent = 1) + s += textbox.line(cols_1, marker = '+', indent = 1) + for msg in messages: + for l in textwrap.wrap(msg, width = width - 3): + s += textbox.row(cols_1, [' ' + l], indent = 1) + for l in textwrap.wrap(messages[msg], width = width - 3 - 4): + s += textbox.row(cols_1, [' ' + l], indent = 1) + s += textbox.line(cols_1, marker = '+', indent = 1) + return s def report(self): - str = '' - sw = sorted(self.warnings.items(), key = operator.itemgetter(1), reverse = True) - for w in sw: - str += ' %5d %s%s' % (w[1], w[0], os.linesep) - return str - - def accumulate(self, total): - for w in self.warnings: - if w not in total.warnings: - total.warnings[w] = self.warnings[w] - else: - total.warnings[w] += self.warnings[w] - total.count += self.count + arch, bsp, build = self._opts() + warnings = self._get_warnings(arch, bsp, build) + total = 0 + for build in warnings: + total += self._total(self.warnings[build]) + if total == 0: + s = ' No warnings' + else: + data = self._analyze(warnings, self.groups['exclude']) + s = self._report_category('By Architecture (total : %d)' % (total), + data['arch'], data['groups']['arch']) + s += os.linesep + s += self._report_category('By BSP (total : %d)' % (total), + data['arch_bsp'], data['groups']['arch_bsp']) + s += os.linesep + s += self._report_category('By Build (total : %d)' % (total), + data['build'], data['groups']['build']) + s += os.linesep + s += self._report_warning_map() + s += os.linesep + + return s + + def set_build(self, arch, bsp, build): + self.arch = arch + self.bsp = bsp + self.build = build + self.build_key = '%s/%s-%s' % (arch, bsp, build) + if self.build_key not in self.warnings: + self.warnings[self.build_key] = {} + if self.build_key not in self.errors: + self.errors[self.build_key] = {} + + def clear_build(self): + self.arch = None + self.bsp = None + self.build = None + self.build_key = None - def get(self): - return self.count + def get_warning_count(self): + return self.warning_count + + def get_error_count(self): + return self.error_count def reset(self): self.warnings = { } - self.count = 0 + self.warning_count = 0 + self.errors = { } + self.error_count = 0 + self.messages = { 'warnings' : { }, 'errors' : { } } + + def get_warning_messages(self, arch = None, bsp = None, build = None): + messages = self.messages['warnings'][self._key(arch, bsp, build)] + return ['%s %s' % (m, messages[m]) for m in messages] + + def get_error_messages(self, arch = None, bsp = None, build = None): + messages = self.messages['errors'][self._key(arch, bsp, build)] + return ['%s %s' % (m, messages[m]) for m in messages] def output(self, text): - for l in text.splitlines(): - if ' warning:' in l: - self.count += 1 - ws = l.split(' ') - if len(ws) > 0: - ws = ws[0].split(':') - w = path.abspath(ws[0]) - w = w.replace(self.rtems, '') - if path.isabspath(w): - w = w[1:] - # - # Ignore compiler option warnings. - # - if len(ws) >= 3: - w = '%s:%s:%s' % (w, ws[1], ws[2]) - if w not in self.warnings: - self.warnings[w] = 0 - self.warnings[w] += 1 + def _line_split(line, source_base): + ls = line.split(' ', 1) + fname = ls[0].split(':') + # + # Ignore compiler option warnings. + # + if len(fname) < 4: + return None + p = path.abspath(fname[0]) + p = p.replace(source_base, '') + if path.isabspath(p): + p = p[1:] + return p, fname[1], fname[2], ls[1] + + if self.build_key is not None and \ + (' warning:' in text or ' error:' in text): + for l in text.splitlines(): + if ' warning:' in l: + self.warning_count += 1 + archive = self.warnings[self.build_key] + messages = 'warnings' + elif ' error:' in l: + self.error_count += 1 + archive = self.errors[self.build_key] + messages = 'errors' + else: + continue + line_parts = _line_split(l, self.rtems) + if line_parts is not None: + src, line, pos, msg = line_parts + where = '%s:%s:%s' % (src, line, pos) + if where not in archive: + archive[where] = 1 + else: + archive[where] += 1 + if self.build_key not in self.messages[messages]: + self.messages[messages][self.build_key] = { } + self.messages[messages][self.build_key][where] = msg + log.output(text) class results: @@ -108,11 +379,11 @@ class results: def _arch_bsp(self, arch, bsp): return '%s/%s' % (arch, bsp) - def add(self, good, arch, bsp, configure, warnings): + def add(self, good, arch, bsp, configure, warnings, error_messages): if good: - self.passes += [(arch, bsp, configure, warnings)] + self.passes += [(arch, bsp, configure, warnings, None)] else: - self.fails += [(arch, bsp, configure, 0)] + self.fails += [(arch, bsp, configure, warnings, error_messages)] def report(self): log.notice('* Passes: %d Failures: %d' % @@ -123,7 +394,7 @@ class results: (len(self.passes), len(self.fails))) log.output(' Failures:') if len(self.fails) == 0: - log.output('None') + log.output(' None') else: max_col = 0 for f in self.fails: @@ -135,12 +406,15 @@ class results: config_at = config_cmd.find('configure') if config_at != -1: config_cmd = config_cmd[config_at:] - log.output(' %*s: %s' % (max_col + 2, - self._arch_bsp(f[0], f[1]), - config_cmd)) + s1 = ' %*s: ' % (max_col + 2, self._arch_bsp(f[0], f[1])) + log.output(wrap([s1, config_cmd], lineend = '\\')) + if f[4] is not None: + s1 = ' ' * len(s1) + for msg in f[4]: + log.output(wrap([s1, msg], lineend = '\\')) log.output(' Passes:') if len(self.passes) == 0: - log.output('None') + log.output(' None') else: max_col = 0 for f in self.passes: @@ -152,10 +426,11 @@ class results: config_at = config_cmd.find('configure') if config_at != -1: config_cmd = config_cmd[config_at:] - log.output(' %*s: %5d %s' % (max_col + 2, - self._arch_bsp(f[0], f[1]), - f[3], - config_cmd)) + log.output(wrap((' %*s: %5d ' % (max_col + 2, + self._arch_bsp(f[0], f[1]), + f[3]), + config_cmd), + lineend = '\\')) class configuration: @@ -163,8 +438,9 @@ class configuration: self.config = configparser.ConfigParser() self.name = None self.archs = { } - self.builds = { } + self.builds_ = { } self.profiles = { } + self.configurations = { } def __str__(self): import pprint @@ -172,7 +448,7 @@ class configuration: s += 'Archs:' + os.linesep + \ pprint.pformat(self.archs, indent = 1, width = 80) + os.linesep s += 'Builds:' + os.linesep + \ - pprint.pformat(self.builds, indent = 1, width = 80) + os.linesep + pprint.pformat(self.builds_, indent = 1, width = 80) + os.linesep s += 'Profiles:' + os.linesep + \ pprint.pformat(self.profiles, indent = 1, width = 80) + os.linesep return s @@ -183,16 +459,16 @@ class configuration: return rec except: if err: - raise error.general('config: no %s found in %s' % (label, section)) + raise error.general('config: no "%s" found in "%s"' % (label, section)) return None def _get_items(self, section, err = True): try: - items = self.config.items(section) + items = [(name, key.replace(os.linesep, ' ')) for name, key in self.config.items(section)] return items except: if err: - raise error.general('config: section %s not found' % (section)) + raise error.general('config: section "%s" not found' % (section)) return [] def _comma_list(self, section, label, error = True): @@ -201,7 +477,37 @@ class configuration: return [] return sorted(set([a.strip() for a in items.split(',')])) - def load(self, name, variation): + def _get_item_names(self, section, err = True): + try: + return [item[0] for item in self.config.items(section)] + except: + if err: + raise error.general('config: section "%s" not found' % (section)) + return [] + + def _build_options(self, build, nesting = 0): + if ':' in build: + section, name = build.split(':', 1) + opts = [self._get_item(section, name)] + return opts + builds = self.builds_['builds'] + if build not in builds: + raise error.general('build %s not found' % (build)) + if nesting > 20: + raise error.general('nesting build %s' % (build)) + options = [] + for option in self.builds_['builds'][build]: + if ':' in option: + section, name = option.split(':', 1) + opts = [self._get_item(section, name)] + else: + opts = self._options(option, nesting + 1) + for opt in opts: + if opt not in options: + options += [opt] + return options + + def load(self, name, build): if not path.exists(name): raise error.general('config: cannot read configuration: %s' % (name)) self.name = name @@ -239,23 +545,44 @@ class configuration: arch[b]['bspopts'] = self._comma_list(a, 'bspopts_%s' % (b), error = False) self.archs[a] = arch builds = {} - builds['default'] = self._get_item('builds', 'default').split() - builds['variations'] = self._comma_list('builds', 'variations') - if variation is None: - variation = builds['default'] - builds['variation'] = variation - builds['base'] = self._get_item('builds', 'standard').split() - builds['variations'] = self._comma_list('builds', variation) - builds['var_options'] = {} - for v in builds['variations']: - if v == 'base': - builds['var_options'][v] = self._get_item('builds', v).split() - else: - builds['var_options'][v] = [] - self.builds = builds + builds['default'] = self._get_item('builds', 'default') + if build is None: + build = builds['default'] + builds['config'] = { } + for config in self._get_items('config'): + builds['config'][config[0]] = config[1] + builds['build'] = build + builds_ = self._get_item_names('builds') + builds['builds'] = {} + for build in builds_: + build_builds = self._comma_list('builds', build) + has_config = False + has_build = False + for b in build_builds: + if ':' in b: + if has_build: + raise error.general('config and build in build: %s' % (build)) + has_config = True + else: + if has_config: + raise error.general('config and build in build: %s' % (build)) + has_build = True + builds['builds'][build] = build_builds + self.builds_ = builds - def variations(self): - return self.builds['variations'] + def build(self): + return self.builds_['build'] + + def builds(self): + if self.builds_['build'] in self.builds_['builds']: + build = self.builds_['builds'][self.builds_['build']] + if ':' in build[0]: + return [self.builds_['build']] + return build + return None + + def build_options(self, build): + return ' '.join(self._build_options(build)) def excludes(self, arch): excludes = self.archs[arch]['excludes'].keys() @@ -286,14 +613,6 @@ class configuration: def bspopts(self, arch, bsp): return self.archs[arch][bsp]['bspopts'] - def base(self): - return self.builds['base'] - - def variant_options(self, variant): - if variant in self.builds['var_options']: - return self.builds['var_options'][variant] - return [] - def profile_present(self, profile): return profile in self.profiles @@ -303,6 +622,133 @@ class configuration: def profile_arch_bsps(self, profile, arch): return self.profiles[profile]['bsps_%s' % (arch)] + def report(self, profiles = True, builds = True, architectures = True): + width = 70 + cols_1 = [width] + cols_2 = [10, width - 10] + s = textbox.line(cols_1, line = '=', marker = '+', indent = 1) + s1 = ' File' + colon = ':' + for l in textwrap.wrap(self.name, width = cols_2[1] - 3): + s += textbox.row(cols_2, [s1, ' ' + l], marker = colon, indent = 1) + colon = ' ' + s1 = ' ' * len(s1) + s += textbox.line(cols_1, marker = '+', indent = 1) + s += textbox.line(cols_1, line = '=', marker = '+', indent = 1) + if profiles: + profiles = sorted(self.profiles['profiles']) + bsps = 0 + for profile in profiles: + archs = sorted(self.profiles[profile]['archs']) + for arch in archs: + bsps += len(self.profiles[profile]['bsps_%s' % (arch)]) + s += textbox.row(cols_1, + [' Profiles : %d/%d' % (len(archs), bsps)], + indent = 1) + for profile in profiles: + textbox.row(cols_2, + [profile, self.profiles[profile]['name']], + indent = 1) + s += textbox.line(cols_1, marker = '+', indent = 1) + for profile in profiles: + s += textbox.row(cols_1, [' %s' % (profile)], indent = 1) + profile = self.profiles[profile] + archs = sorted(profile['archs']) + for arch in archs: + s += textbox.line(cols_2, marker = '+', indent = 1) + s1 = ' ' + arch + for l in textwrap.wrap(', '.join(profile['bsps_%s' % (arch)]), + width = cols_2[1] - 2): + s += textbox.row(cols_2, [s1, ' ' + l], indent = 1) + s1 = ' ' * len(s1) + s += textbox.line(cols_2, marker = '+', indent = 1) + if builds: + s += textbox.line(cols_1, line = '=', marker = '+', indent = 1) + s += textbox.row(cols_1, + [' Builds: %s (default)' % (self.builds_['default'])], + indent = 1) + builds = self.builds_['builds'] + bsize = 0 + for build in builds: + if len(build) > bsize: + bsize = len(build) + cols_b = [bsize + 2, width - bsize - 2] + s += textbox.line(cols_b, marker = '+', indent = 1) + for build in builds: + s1 = ' ' + build + for l in textwrap.wrap(', '.join(builds[build]), + width = cols_b[1] - 2): + s += textbox.row(cols_b, [s1, ' ' + l], indent = 1) + s1 = ' ' * len(s1) + s += textbox.line(cols_b, marker = '+', indent = 1) + configs = self.builds_['config'] + s += textbox.row(cols_1, + [' Configure Options: %d' % (len(configs))], + indent = 1) + csize = 0 + for config in configs: + if len(config) > csize: + csize = len(config) + cols_c = [csize + 3, width - csize - 3] + s += textbox.line(cols_c, marker = '+', indent = 1) + for config in configs: + s1 = ' ' + config + for l in textwrap.wrap(configs[config], width = cols_c[1] - 3): + s += textbox.row(cols_c, [s1, ' ' + l], indent = 1) + s1 = ' ' * len(s1) + s += textbox.line(cols_c, marker = '+', indent = 1) + if architectures: + s += textbox.line(cols_1, line = '=', marker = '+', indent = 1) + archs = sorted(self.archs.keys()) + bsps = 0 + asize = 0 + for arch in archs: + if len(arch) > asize: + asize = len(arch) + bsps += len(self.archs[arch]['bsps']) + s += textbox.row(cols_1, + [' Architectures : %d (bsps: %d)' % (len(archs), bsps)], + indent = 1) + cols_a = [asize + 2, width - asize - 2] + s += textbox.line(cols_a, marker = '+', indent = 1) + for arch in archs: + s += textbox.row(cols_a, + [' ' + arch, ' %d' % (len(self.archs[arch]['bsps']))], + indent = 1) + s += textbox.line(cols_a, marker = '+', indent = 1) + for archn in archs: + arch = self.archs[archn] + if len(arch['bsps']) > 0: + bsize = 0 + for bsp in arch['bsps']: + if len(bsp) > bsize: + bsize = len(bsp) + cols_b = [bsize + 3, width - bsize - 3] + s += textbox.row(cols_1, [' ' + archn + ':'], indent = 1) + s += textbox.line(cols_b, marker = '+', indent = 1) + for bsp in arch['bsps']: + s1 = ' ' + bsp + bspopts = ' '.join(arch[bsp]['bspopts']) + if len(bspopts): + for l in textwrap.wrap('bopt: ' + bspopts, + width = cols_b[1] - 3): + s += textbox.row(cols_b, [s1, ' ' + l], indent = 1) + s1 = ' ' * len(s1) + excludes = [] + for exclude in arch['excludes']: + if bsp in arch['excludes'][exclude]: + excludes += [exclude] + excludes = ', '.join(excludes) + if len(excludes): + for l in textwrap.wrap('ex: ' + excludes, + width = cols_b[1] - 3): + s += textbox.row(cols_b, [s1, ' ' + l], indent = 1) + s1 = ' ' * len(s1) + if len(bspopts) == 0 and len(excludes) == 0: + s += textbox.row(cols_b, [s1, ' '], indent = 1) + s += textbox.line(cols_b, marker = '+', indent = 1) + return s + class build: def __init__(self, config, version, prefix, tools, rtems, build_dir, options): @@ -315,12 +761,13 @@ class build: self.options = options self.errors = { 'configure': 0, 'build': 0, - 'tests': 0 } + 'tests': 0, + 'fails': []} self.counts = { 'h' : 0, 'exes' : 0, 'objs' : 0, 'libs' : 0 } - self.warnings = warnings_counter(rtems) + self.warnings_errors = warnings_errors(rtems) self.results = results() if not path.exists(path.join(rtems, 'configure')) or \ not path.exists(path.join(rtems, 'Makefile.in')) or \ @@ -340,22 +787,18 @@ class build: def _bsps(self, arch): return self.config.arch_bsps(arch) - def _variations(self, arch, bsp): - def _match(var, vars): - matches = [] - for v in vars: - if var in v.split('-'): - matches += [v] - return matches - - vars = self.config.variations() - for v in self.config.excludes(arch): - for m in _match(v, vars): - vars.remove(m) - for v in self.config.bsp_excludes(arch, bsp): - for m in _match(v, vars): - vars.remove(m) - return vars + def _build(self): + return self.config.build() + + def _builds(self, arch, bsp): + builds = self.config.builds() + if builds is None: + return None + for b in self.config.excludes(arch): + builds.remove(b) + for b in self.config.bsp_excludes(arch, bsp): + builds.remove(b) + return builds def _arch_bsp_dir_make(self, arch, bsp): if not path.exists(self._path(arch, bsp)): @@ -366,6 +809,8 @@ class build: path.removeall(self._path(arch, bsp)) def _config_command(self, commands, arch, bsp): + if type(commands) is not list: + commands = [commands] cmd = [path.join(self.rtems, 'configure')] commands += self.config.bspopts(arch, bsp) for c in commands: @@ -376,11 +821,10 @@ class build: cmd += [c] return ' '.join(cmd) - def _build_set(self, variations): + def _build_set(self, builds): build_set = { } - bs = self.config.base() - for var in variations: - build_set[var] = bs + self.config.variant_options(var) + for build in builds: + build_set[build] = self.config.build_options(build) return build_set def _build_dir(self, arch, bsp, build): @@ -406,6 +850,59 @@ class build: self.counts[f] += counts[f] return counts + def _have_failures(self, fails): + return len(fails) != 0 + + def _warnings_report(self): + if self.options['warnings-report'] is not None: + with open(self.options['warnings-report'], 'w') as f: + f.write(title() + os.linesep) + f.write(os.linesep) + f.write('Date: %s%s' % (datetime.date.today().strftime('%c'), os.linesep)) + f.write(os.linesep) + f.write(command_line() + os.linesep) + f.write(self.warnings_errors.report()) + + def _finished(self): + log.notice('+ warnings:%d exes:%d objs:%d libs:%d' % \ + (self.warnings_errors.get_warning_count(), self.counts['exes'], + self.counts['objs'], self.counts['libs'])) + log.output() + log.output('Warnings:') + log.output(self.warnings_errors.report()) + log.output() + log.notice('Failures:') + log.notice(self.failures_report(self.errors['fails'])) + self._warnings_report() + + def failures_report(self, fails): + if not self._have_failures(fails): + return ' No failure(s)' + absize = 0 + bsize = 0 + ssize = 0 + for f in fails: + arch_bsp = '%s/%s' % (f[1], f[2]) + if len(arch_bsp) > absize: + absize = len(arch_bsp) + if len(f[3]) > bsize: + bsize = len(f[3]) + if len(f[0]) > ssize: + ssize = len(f[0]) + fc = 1 + s = '' + for f in fails: + fcl = '%3d' % (fc) + arch_bsp = '%s/%s' % (f[1], f[2]) + state = f[0] + s1 = '%s %-*s %-*s %-*s ' % (fcl, bsize, f[3], absize, arch_bsp, ssize, state) + s += wrap((s1, f[4]), lineend = '\\') + s1 = ' ' * (len(s1) + 2) + for e in self.warnings_errors.get_error_messages(f[1], f[2], f[3]): + s += wrap([s1, 'error: ' + e]) + fc += 1 + return s + def build_arch_bsp(self, arch, bsp): if not self.config.bsp_present(arch, bsp): raise error.general('BSP not found: %s/%s' % (arch, bsp)) @@ -414,37 +911,44 @@ class build: log.notice('. Creating: %s' % (self._path(arch, bsp))) self._arch_bsp_dir_clean(arch, bsp) self._arch_bsp_dir_make(arch, bsp) - variations = self._variations(arch, bsp) - build_set = self._build_set(variations) + builds = self._builds(arch, bsp) + if builds is None: + raise error.general('build not found: %s' % (self._build())) + build_set = self._build_set(builds) bsp_start = datetime.datetime.now() - bsp_warnings = warnings_counter(self.rtems) env_path = os.environ['PATH'] os.environ['PATH'] = path.host(path.join(self.tools, 'bin')) + \ os.pathsep + os.environ['PATH'] + fails = [] for bs in sorted(build_set.keys()): - warnings = warnings_counter(self.rtems) + self.warnings_errors.set_build(arch, bsp, bs) start = datetime.datetime.now() log.output('- ' * 35) log.notice('. Configuring: %s' % (bs)) try: + warnings = self.warnings_errors.get_warning_count() result = '+ Pass' bpath = self._build_dir(arch, bsp, bs) + good = True + error_messages = None path.mkdir(bpath) config_cmd = self._config_command(build_set[bs], arch, bsp) cmd = config_cmd - e = execute.capture_execution(log = warnings) - log.output('run: ' + cmd) + e = execute.capture_execution(log = self.warnings_errors) + log.output(wrap(('run: ', cmd), lineend = '\\')) if self.options['dry-run']: exit_code = 0 else: exit_code, proc, output = e.shell(cmd, cwd = path.host(bpath)) if exit_code != 0: result = '- FAIL' + failure = ('configure', arch, bsp, bs, config_cmd) + fails += [failure] self.errors['configure'] += 1 + self.errors['fails'] += [failure] log.notice('- Configure failed: %s' % (bs)) log.output('cmd failed: %s' % (cmd)) - if self.options['stop-on-error']: - raise error.general('Configuring %s failed' % (bs)) + good = False else: log.notice('. Building: %s' % (bs)) cmd = 'make' @@ -456,34 +960,43 @@ class build: else: exit_code, proc, output = e.shell(cmd, cwd = path.host(bpath)) if exit_code != 0: + error_messages = self.warnings_errors.get_error_messages() result = '- FAIL' + failure = ('build', arch, bsp, bs, config_cmd, error_messages) + fails += [failure] self.errors['build'] += 1 + self.errors['fails'] += [failure] log.notice('- FAIL: %s: %s' % (bs, self._error_str())) log.output('cmd failed: %s' % (cmd)) - if self.options['stop-on-error']: - raise error.general('Building %s failed' % (bs)) + good = False files = self._count_files(arch, bsp, bs) log.notice('%s: %s: warnings:%d exes:%d objs:%s libs:%d' % \ - (result, bs, warnings.get(), + (result, bs, + self.warnings_errors.get_warning_count() - warnings, files['exes'], files['objs'], files['libs'])) log.notice(' %s' % (self._error_str())) - self.results.add(result[0] == '+', arch, bsp, config_cmd, warnings.get()) + self.results.add(good, arch, bsp, config_cmd, + self.warnings_errors.get_warning_count() - warnings, + error_messages) + if not good and self.options['stop-on-error']: + raise error.general('Configuring %s failed' % (bs)) finally: end = datetime.datetime.now() if not self.options['no-clean']: log.notice('. Cleaning: %s' % (self._build_dir(arch, bsp, bs))) path.removeall(self._build_dir(arch, bsp, bs)) log.notice('^ Time %s' % (str(end - start))) - log.output('Warnings Report:') - log.output(warnings.report()) - warnings.accumulate(bsp_warnings) - warnings.accumulate(self.warnings) + self.warnings_errors.clear_build() bsp_end = datetime.datetime.now() log.notice('^ BSP Time %s' % (str(bsp_end - bsp_start))) - log.output('BSP Warnings Report:') - log.output(bsp_warnings.report()) + log.output('Failure Report:') + log.output(self.failures_report(fails)) os.environ['PATH'] = env_path + def build_bsp(self, arch, bsp): + self.build_arch_bsp(arch, bsp) + self._finished() + def build_arch(self, arch): start = datetime.datetime.now() log.output('=' * 70) @@ -494,21 +1007,13 @@ class build: self.build_arch_bsp(arch, bsp) end = datetime.datetime.now() log.notice('^ Architecture Time %s' % (str(end - start))) - log.notice(' warnings:%d exes:%d objs:%d libs:%d' % \ - (self.warnings.get(), self.counts['exes'], - self.counts['objs'], self.counts['libs'])) - log.output('Architecture Warnings:') - log.output(self.warnings.report()) + self._finished() def build(self): for arch in self.config.archs(): self.build_arch(arch) log.notice('^ Profile Time %s' % (str(end - start))) - log.notice('+ warnings:%d exes:%d objs:%d libs:%d' % \ - (self.warnings.get(), self.counts['exes'], - self.counts['objs'], self.counts['libs'])) - log.output('Profile Warnings:') - log.output(self.warnings.report()) + self._finished() def build_profile(self, profile): if not self.config.profile_present(profile): @@ -520,11 +1025,7 @@ class build: self.build_arch_bsp(arch, bsp) end = datetime.datetime.now() log.notice('^ Profile Time %s' % (str(end - start))) - log.notice(' warnings:%d exes:%d objs:%d libs:%d' % \ - (self.warnings.get(), self.counts['exes'], - self.counts['objs'], self.counts['libs'])) - log.output('Profile Warnings:') - log.output(self.warnings.report()) + self._finished() def run_args(args): b = None @@ -545,16 +1046,26 @@ def run_args(args): prefix = '/opt/rtems/%s' % (rtems_version()) tools = prefix build_dir = 'bsp-builds' - logf = 'bsp-build-%s.txt' % (datetime.datetime.now().strftime('%Y%m%d-%H%M%S')) - config_file = path.join(top, 'share', 'rtems', 'tester', 'rtems', 'rtems-bsps.ini') + logf = 'bsp-build-%s.txt' % \ + (datetime.datetime.now().strftime('%Y%m%d-%H%M%S')) + config_file = path.join(top, 'share', 'rtems', 'tester', + 'rtems', 'rtems-bsps.ini') if not path.exists(config_file): config_file = path.join(top, 'tester', 'rtems', 'rtems-bsps.ini') argsp = argparse.ArgumentParser() - argsp.add_argument('--prefix', help = 'Prefix to build the BSP.', type = str) - argsp.add_argument('--rtems-tools', help = 'The RTEMS tools directory.', type = str) - argsp.add_argument('--rtems', help = 'The RTEMS source tree.', type = str) - argsp.add_argument('--build-path', help = 'Path to build in.', type = str) + argsp.add_argument('--prefix', help = 'Prefix to build the BSP.', + type = str) + argsp.add_argument('--rtems-tools', help = 'The RTEMS tools directory.', + type = str) + argsp.add_argument('--rtems', help = 'The RTEMS source tree.', + type = str) + argsp.add_argument('--config-report', help = 'Report the configuration.', + action = 'store_true') + argsp.add_argument('--warnings-report', help = 'Report the warnings to a file.', + type = str, default = None) + argsp.add_argument('--build-path', help = 'Path to build in.', + type = str) argsp.add_argument('--log', help = 'Log file.', type = str) argsp.add_argument('--stop-on-error', help = 'Stop on an error.', action = 'store_true') @@ -562,9 +1073,14 @@ def run_args(args): action = 'store_true') argsp.add_argument('--profiles', help = 'Build the listed profiles.', type = str, default = 'tier-1') - argsp.add_argument('--build', help = 'Build variation.', type = str, default='all') - argsp.add_argument('--arch', help = 'Build the specific architecture.', type = str) - argsp.add_argument('--bsp', help = 'Build the specific BSP.', type = str) + argsp.add_argument('--build', help = 'Build name to build.', + type = str, default='all') + argsp.add_argument('--arch', help = 'Build the specific architecture.', + type = str) + argsp.add_argument('--bsp', help = 'Build the specific BSP.', + type = str) + argsp.add_argument('--jobs', help = 'Number of jobs to run.', + type = int, default = host.cpus()) argsp.add_argument('--dry-run', help = 'Do not run the actual builds.', action = 'store_true') @@ -572,7 +1088,8 @@ def run_args(args): if opts.log is not None: logf = opts.log log.default = log.log([logf]) - log.notice('RTEMS Tools Project - RTEMS Kernel BSP Builder, %s' % (version.str())) + log.notice(title()) + log.output(command_line()) if opts.rtems is None: raise error.general('No RTEMS source provided on the command line') if opts.prefix is not None: @@ -587,16 +1104,22 @@ def run_args(args): config = configuration() config.load(config_file, opts.build) - options = { 'stop-on-error' : opts.stop_on_error, - 'no-clean' : opts.no_clean, - 'dry-run' : opts.dry_run, - 'jobs' : 8 } + if opts.config_report: + log.notice('Configuration Report:') + log.notice(config.report()) + sys.exit(0) + + options = { 'stop-on-error' : opts.stop_on_error, + 'no-clean' : opts.no_clean, + 'dry-run' : opts.dry_run, + 'jobs' : opts.jobs, + 'warnings-report' : opts.warnings_report } b = build(config, rtems_version(), prefix, tools, path.shell(opts.rtems), build_dir, options) if opts.arch is not None: if opts.bsp is not None: - b.build_arch_bsp(opts.arch, opts.bsp) + b.build_bsp(opts.arch, opts.bsp) else: b.build_arch(opts.arch) else: diff --git a/tester/rtems/rtems-bsps.ini b/tester/rtems/rtems-bsps.ini index ad63e6d..a88aced 100644 --- a/tester/rtems/rtems-bsps.ini +++ b/tester/rtems/rtems-bsps.ini @@ -1,9 +1,9 @@ # # RTEMS Tools Project (http://www.rtems.org/) -# Copyright 2016 Chris Johns (chrisj@rtems.org) +# Copyright 2016-2017 Chris Johns (chrisj@rtems.org) # All rights reserved. # -# This file is part of the RTEMS Tools package in 'rtems-bdp-builder'. +# This file is part of the RTEMS Tools package in 'rtems-bsp-builder'. # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -29,7 +29,7 @@ bsps_arm = altcycv_devkit, altcycv_devkit_smp, xilinx_zynq_zc702, xilinx_zynq_zc706, xilinx_zynq_zedboard bsps_i386 = pc686 -bsps_sparc = erc32 +bsps_sparc = leon2, leon3 # # Tier 2: no build errors and no unexpected tests failures on hardware and @@ -40,8 +40,7 @@ archs = arm, sparc bsps_arm = lm3s6965_qemu, realview_pbx_a9_qemu, realview_pbx_a9_qemu_smp, xilinx_zynq_a9_qemu -bsps_sparc = sis - +bsps_sparc = erc32 # # Tier 3: no build errors, no tests run. # @@ -143,7 +142,7 @@ bspopts_pc686 = BSP_PRINT_EXCEPTION_CONTEXT=1 bsps = moxiesim [sparc] -bsps = erc32, sis +bsps = erc32, leon2, leon3 [powerpc] bsps = @@ -160,45 +159,65 @@ bsps = epiphany-sim # default = all # -# The variations, basic is just a BSP build and all is the maximum number of -# variations. +# A default build with tests # -variations = basic, all +tests = config:base, config:tests # -# The variations, default is Yes and can be overriden in an architecture. +# A default build without tests # -basic = standard +no-tests = config:base # # The all, default is Yes and can be overriden in an architecture. # all = debug, profiling, smp, smp-debug, - posix, no-posix, posix-debug, posix-profiling, posix-smp, - network, no-network, network-debug, smp-network, smp-network-debug + posix, no-posix, posix-debug, posix-profiling, + network, no-network, network-debug, + smp-network, smp-network-debug +# +# The options for each varations. +# +debug = config:base, config:tests, config:debug +profiling = config:base, config:tests, config:profiling +smp = config:base, config:tests, config:smp +smp-debug = config:base, config:tests, config:smp, config:debug +posix = config:base, config:tests, config:posix +no-posix = config:base, config:tests, config:no-posix +posix-debug = config:base, config:tests, config:posix, config:debug +posix-profiling = config:base, config:tests, config:posix, config:profiling +network = config:base, config:tests, config:network +no-network = config:base, config:tests, config:no-network +network-debug = config:base, config:tests, config:network, config:debug +smp-network = config:base, config:tests, config:smp, config:network +smp-network-debug = config:base, config:tests, config:smp, config:network, + config:debug + +# +# The config section holds the configuration options used in the builds. +# +[config] +# +# Base set of configure options every build needs. +# +base = --target=@ARCH@-rtems@RTEMS_VERSION@ + --enable-rtemsbsp=@BSP@ + --prefix=@PREFIX@ # -# The base build options, tests the default configuration, all variations are -# added on. +# Tests. # -standard = --target=@ARCH@-rtems@RTEMS_VERSION@ - --enable-rtemsbsp=@BSP@ - --prefix=@PREFIX@ - --enable-tests +tests = --enable-tests # # The options for each varations. # debug = --enable-debug +no-debug = --disable-debug profiling = --enable-profiling +no-profiling = --disable-profiling smp = --enable-smp -smp-debug = --enable-smp --enable-debug +no-smp = --disable-smp posix = --enable-posix no-posix = --disable-posix -posix-debug = --enable-posix --enable-debug -posix-profiling = --enable-posix --enable-profiling -posix-smp = --enable-posix --enable-smp network = --enable-networking no-network = --disable-networking -network-debug = --enable-debug --enable-networking -smp-network = --enable-smp --enable-networking -smp-network-debug = --enable-smp --enable-debug --enable-networking # # Define how each profile is to be treated. Note, hardware vs simulator testing |