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

4.105
Last change on this file since f7f0704 was f7f0704, checked in by Chris Johns <chrisj@…>, on 08/25/16 at 06:13:18

bsp-builder: Fix column printing of the arch/bsp.

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