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

Last change on this file since b89a7e4b was b89a7e4b, checked in by Chris Johns <chrisj@…>, on Apr 24, 2017 at 10:40:19 PM

rtems-bsp-builder: Fix the build sets excludes.

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