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

5
Last change on this file since 0813c87 was 0813c87, checked in by Chris Johns <chrisj@…>, on 09/04/18 at 00:15:46

tester/bsp-builder: Fix the build jobs with more than one BSP exclude.

Excluding more than one build resulted in common builds being removed
resulting in a list remove error.

  • Property mode set to 100755
File size: 62.5 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2016-2018 Chris Johns (chrisj@rtems.org)
4# All rights reserved.
5#
6# This file is part of the RTEMS Tools package in 'rtems-tools'.
7#
8# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions are met:
10#
11# 1. Redistributions of source code must retain the above copyright notice,
12# this list of conditions and the following disclaimer.
13#
14# 2. Redistributions in binary form must reproduce the above copyright notice,
15# this list of conditions and the following disclaimer in the documentation
16# and/or other materials provided with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29#
30
31from __future__ import print_function
32
33import argparse
34import copy
35import datetime
36import operator
37import os
38import re
39import sys
40import textwrap
41import threading
42import time
43import traceback
44
45import pprint
46
47from rtemstoolkit import configuration
48from rtemstoolkit import execute
49from rtemstoolkit import error
50from rtemstoolkit import host
51from rtemstoolkit import log
52from rtemstoolkit import mailer
53from rtemstoolkit import path
54from rtemstoolkit import textbox
55from rtemstoolkit import version
56
57#
58# Group loggin entries together.
59#
60log_lock = threading.Lock()
61
62#
63# The max build label size in the jobs list.
64#
65max_build_label = 0
66
67def rtems_version():
68    return version.version()
69
70def wrap(line, lineend = '', indent = 0, width = 75):
71    if type(line) is tuple or type(line) is list:
72        if len(line) >= 2:
73            s1 = line[0]
74        else:
75            s1 = ''
76        s2 = line[1:]
77    elif type(line) is str:
78        s1 = ''
79        s2 = [line]
80    else:
81        raise error.internal('line is not a tuple, list or string')
82    s1len = len(s1)
83    s = ''
84    first = True
85    for ss in s2:
86        if type(ss) is not str and type(ss) is not unicode:
87            raise error.internal('text needs to be a string')
88        for l in textwrap.wrap(ss, width = width - s1len - indent - 1):
89            s += '%s%s%s%s%s' % (' ' * indent, s1, l, lineend, os.linesep)
90            if first and s1len > 0:
91                s1 = ' ' * s1len
92    if lineend != '':
93        s = s[:0 - len(os.linesep) - 1] + os.linesep
94    return s
95
96def comma_split(options):
97    if options is None:
98        return None
99    return [o.strip() for o in options.split(',')]
100
101def title():
102    return 'RTEMS Tools Project - RTEMS Kernel BSP Builder, %s' % (version.str())
103
104def command_line():
105    return wrap(('command: ', ' '.join(sys.argv)), lineend = '\\')
106
107def jobs_option_parse(jobs_option):
108    try:
109        if '/' not in jobs_option:
110            return 1, int(jobs_option)
111        jos = jobs_option.split('/')
112        if len(jos) != 2:
113            raise error.general('invalid jobs option: %s' % (jobs_option))
114        return int(jos[0]), int(jos[1])
115    except:
116        pass
117    raise error.general('invalid jobs option: %s' % (jobs_option))
118
119def arch_bsp_build_parse(build):
120    if type(build) is str:
121        build_key = build
122    else:
123        build_key = build.key()
124    abb = build_key.split('.')
125    if len(abb) != 2:
126        raise error.general('invalid build key: %s' % (build_key))
127    ab = abb[0].split('/')
128    if len(ab) != 2:
129        raise error.general('invalid build key: %s' % (build_key))
130    return ab[0], ab[1], abb[1]
131
132def set_max_build_label(jobs):
133    global max_build_label
134    for job in jobs:
135        if len(job.build.key()) > max_build_label:
136            max_build_label = len(job.build.key())
137    max_build_label += 2
138
139class arch_bsp_build:
140
141    def __init__(self, arch, bsp, build, build_config):
142        self.arch = arch
143        self.bsp = bsp
144        self.build = build
145        self.build_config = build_config
146        self.start_time = None
147        self.stop_time = None
148
149    def __str__(self):
150        return self.key() + ': ' + self.build_config
151
152    def key(self):
153        return '%s/%s.%s' % (self.arch, self.bsp, self.build)
154
155    def get_arch_bsp(self):
156        return self.arch, self.bsp
157
158    def start(self):
159        self.start_time = datetime.datetime.now()
160
161    def stop(self):
162        self.stop_time = datetime.datetime.now()
163
164    def duration(self):
165        return self.stop_time - self.start_time
166
167class output_worker:
168
169    def __init__(self, we, build):
170        self.text = []
171        self.warnings_errors = we
172        self.build = build
173
174    def output(self, text):
175        self.warnings_errors.process_output(text, self.build)
176        self.text += text.splitlines()
177
178    def log_output(self, heading):
179        log_lock.acquire()
180        try:
181            log.output(heading + self.text)
182        except:
183            raise
184        finally:
185            log_lock.release()
186
187class warnings_errors:
188
189    def __init__(self, source_base, groups):
190        self.lock = threading.Lock()
191        self.source_base = path.host(source_base)
192        self.groups = groups
193        self.reset()
194
195    def _get_warnings(self, build):
196        self.lock.acquire()
197        warnings = [w for w in self.warnings]
198        self.lock.release()
199        return sorted(warnings)
200
201    def _total(self, archive):
202        total = 0
203        for a in archive:
204            total += archive[a]
205        return total
206
207    def _analyze(self, warnings, exclude):
208        def _group(data, category, name, warning, count, groups, group_regx):
209            if 'groups' not in data:
210                data['groups'] = { }
211            if category not in data['groups']:
212                data['groups'][category] = { }
213            if 'totals' not in data['groups'][category]:
214                data['groups'][category]['totals'] = { }
215            if name not in data['groups'][category]:
216                data['groups'][category][name] = { }
217            for group in groups:
218                if group not in data['groups'][category]['totals']:
219                    data['groups'][category]['totals'][group] = 0
220                if group not in data['groups'][category][name]:
221                    data['groups'][category][name][group] = 0
222                if group_regx[group].match(warning):
223                    data['groups'][category][name][group] += count
224                    data['groups'][category]['totals'][group] += count
225                    break
226
227        def _update(data, category, name, warning, count, groups, group_regx):
228            if category not in data:
229                data[category] = { }
230            if name not in data[category]:
231                data[category][name] = { }
232            if warning not in data[category][name]:
233                data[category][name][warning] = 0
234            data[category][name][warning] += count
235            _group(data, category, name,  w, count, groups, group_regx)
236
237        categories = ['arch', 'arch_bsp', 'build']
238        data = { 'groups': { } }
239        for category in categories:
240            data[category] = { }
241            data['groups'][category] = { }
242        group_regx = { }
243        for group in self.groups['groups']:
244            group_regx[group] = re.compile(self.groups[group])
245        exclude_regx = re.compile(exclude)
246        for warning in self.warnings:
247            arch, bsp, build = arch_bsp_build_parse(warning)
248            arch_bsp = '%s/%s' % (arch, bsp)
249            for w in self.warnings[warning]:
250                if not exclude_regx.match(w):
251                    count = self.warnings[warning][w]
252                    _update(data, 'arch', arch, w, count,
253                           self.groups['groups'], group_regx)
254                    _update(data, 'arch_bsp', arch_bsp, w, count,
255                           self.groups['groups'], group_regx)
256                    _update(data, 'build', build, w, count,
257                           self.groups['groups'], group_regx)
258        for category in categories:
259            common = {}
260            for name in data[category]:
261                for w in data[category][name]:
262                    if w not in common:
263                        for other in [n for n in data[category] if n != name]:
264                            if w in data[category][other]:
265                                common[w] = data[category][name][w]
266                                _group(data, category, 'common', w, common[w],
267                                       self.groups['groups'], group_regx)
268            data[category]['common'] = common
269        return data
270
271    def _report_category(self, label, warnings, group_counts, summary):
272        width = 70
273        cols_1 = [width]
274        cols_2 = [8, width - 8]
275        cols_4 = textbox.even_columns(4, width)
276        cols_2_4 = textbox.merge_columns([cols_2, cols_4])
277        s = textbox.line(cols_1, line = '=', marker = '+', indent = 1)
278        s += textbox.row(cols_1, [' ' + label], indent = 1)
279        s += textbox.line(cols_1, marker = '+', indent = 1)
280        builds = ['common'] + sorted([b for b in warnings if b != 'common'])
281        common = warnings['common']
282        for build in builds:
283            build_warnings = warnings[build]
284            if build is not 'common':
285                build_warnings = [w for w in build_warnings if w not in common]
286            s += textbox.row(cols_1,
287                             [' %s : %d warning(s)' % (build,
288                                                       len(build_warnings))],
289                             indent = 1)
290            if len(build_warnings) == 0:
291                s += textbox.line(cols_1, marker = '+', indent = 1)
292            else:
293                s += textbox.line(cols_4, marker = '+', indent = 1)
294                if build not in group_counts:
295                    gs = [0 for group in self.groups['groups']]
296                else:
297                    gs = []
298                    for g in range(0, len(self.groups['groups'])):
299                        group = self.groups['groups'][g]
300                        if group in group_counts[build]:
301                            count = group_counts[build][group]
302                        else:
303                            count = 0
304                        gs += ['%*s' % (cols_4[g % 4] - 2,
305                                        '%s : %4d' % (group, count))]
306                    for row in range(0, len(self.groups['groups']), 4):
307                        if row + 4 > len(self.groups['groups']):
308                            d = gs[row:] + \
309                                ['' for r in range(row,
310                                                   len(self.groups['groups']))]
311                        else:
312                            d = gs[row:+4]
313                        s += textbox.row(cols_4, d, indent = 1)
314                s += textbox.line(cols_2_4, marker = '+', indent = 1)
315                if not summary:
316                    vw = sorted([(w, warnings[build][w]) for w in build_warnings],
317                                key = operator.itemgetter(1),
318                                reverse = True)
319                    for w in vw:
320                        c1 = '%6d' % w[1]
321                        for l in textwrap.wrap(' ' + w[0], width = cols_2[1] - 3):
322                            s += textbox.row(cols_2, [c1, l], indent = 1)
323                            c1 = ' ' * 6
324                    s += textbox.line(cols_2, marker = '+', indent = 1)
325        return s
326
327    def _report_warning_map(self):
328        builds = self.messages['warnings']
329        width = 70
330        cols_1 = [width]
331        s = textbox.line(cols_1, line = '=', marker = '+', indent = 1)
332        s += textbox.row(cols_1, [' Warning Map'], indent = 1)
333        s += textbox.line(cols_1, marker = '+', indent = 1)
334        for build in builds:
335            messages = builds[build]
336            s += textbox.row(cols_1, [' %s : %d' % (build, len(messages))], indent = 1)
337            s += textbox.line(cols_1, marker = '+', indent = 1)
338            for msg in messages:
339                for l in textwrap.wrap(msg, width = width - 3):
340                    s += textbox.row(cols_1, [' ' + l], indent = 1)
341                for l in textwrap.wrap(messages[msg], width = width - 3 - 4):
342                    s += textbox.row(cols_1, ['    ' + l], indent = 1)
343            s += textbox.line(cols_1, marker = '+', indent = 1)
344        return s
345
346    def warnings_report(self, summary = False):
347        self.lock.acquire()
348        s = ' No warnings' + os.linesep
349        try:
350            total = 0
351            for build in self.warnings:
352                total += self._total(self.warnings[build])
353            if total != 0:
354                data = self._analyze(self.warnings, self.groups['exclude'])
355                s = self._report_category('By Architecture (total : %d)' % (total),
356                                          data['arch'], data['groups']['arch'],
357                                          summary)
358                s += os.linesep
359                s += self._report_category('By BSP (total : %d)' % (total),
360                                           data['arch_bsp'], data['groups']['arch_bsp'],
361                                           summary)
362                s += os.linesep
363                s += self._report_category('By Build (total : %d)' % (total),
364                                           data['build'], data['groups']['build'],
365                                           summary)
366                s += os.linesep
367                if not summary:
368                    s += self._report_warning_map()
369                    s += os.linesep
370        finally:
371            self.lock.release()
372        return s
373
374    def clear_build(self, build):
375        self.lock.acquire()
376        self.warnings[build.key()] = {}
377        self.errors[build.key()] = {}
378        self.lock.release()
379
380    def get_warning_count(self):
381        self.lock.acquire()
382        count = self.warning_count
383        self.lock.release()
384        return count
385
386    def get_error_count(self):
387        self.lock.acquire()
388        count = self.error_count
389        self.lock.release()
390        return count
391
392    def reset(self):
393        self.lock.acquire()
394        self.warnings = { }
395        self.warning_count = 0
396        self.errors = { }
397        self.error_count = 0
398        self.messages = { 'warnings' : { }, 'errors' : { } }
399        self.lock.release()
400
401    def _get_messages(self, build, key):
402        self.lock.acquire()
403        if type(build) is str:
404            build_key = build
405        else:
406            build_key = build.key()
407        if build_key not in self.messages[key]:
408            messages = []
409        else:
410            messages = self.messages[key][build_key]
411        messages = ['%s %s' % (m, messages[m]) for m in messages]
412        self.lock.release()
413        return messages
414
415    def get_warning_messages(self, build):
416        return self._get_messages(build, 'warning')
417
418    def get_error_messages(self, build):
419        return self._get_messages(build, 'errors')
420
421    def process_output(self, text, build):
422        def _line_split(line, source_base):
423            if line.count(':') < 2:
424                return None
425            ls = line.split(' ', 1)
426            fname = ls[0].strip().split(':', 2)
427            if len(fname) != 3:
428                return None
429            p = path.abspath(fname[0])
430            p = p.replace(source_base, '')
431            if path.isabspath(p):
432                p = p[1:]
433            if len(fname[2]) == 0:
434                pos = None
435            else:
436                pos = fname[2]
437            return p, fname[1], pos, ls[1]
438
439        def _create_build_errors(build, archive):
440            if build.key() not in archive:
441                archive[build.key()] = { }
442            return archive[build.key()]
443
444        #
445        # The GNU linker does not supply 'error' in error messages. There is no
446        # line information which is understandable. Look for `bin/ld:` and
447        # `collect2:` in the output and then create the error when `collect2:`
448        # is seen.
449        #
450        # The order we inspect each line is important.
451        #
452        if ' warning:' in text or \
453           ' error:' in text or \
454           ' Error:' in text or \
455           'bin/ld:' in text:
456            self.lock.acquire()
457            try:
458                for l in text.splitlines():
459                    if 'bin/ld:' in l:
460                        archive =_create_build_errors(build, self.errors)
461                        if 'linker' not in archive:
462                            archive['linker'] = []
463                        archive['linker'] += [l.split(':', 1)[1].strip()]
464                        messages = 'errors'
465                    elif l.startswith('collect2:'):
466                        archive =_create_build_errors(build, self.errors)
467                        l = '/ld/collect2:0: error: '
468                        if 'linker' not in archive or len(archive['linker']) == 0:
469                            l += 'no error message found!'
470                        else:
471                            l += '; '.join(archive['linker'])
472                            archive['linker'] = []
473                        messages = 'errors'
474                    elif ' warning:' in l:
475                        self.warning_count += 1
476                        archive = _create_build_errors(build, self.warnings)
477                        messages = 'warnings'
478                    elif ' error:' in l or ' Error:' in l:
479                        self.error_count += 1
480                        archive =_create_build_errors(build, self.errors)
481                        messages = 'errors'
482                    else:
483                        continue
484                    line_parts = _line_split(l, self.source_base)
485                    if line_parts is not None:
486                        src, line, pos, msg = line_parts
487                        if pos is not None:
488                            where = '%s:%s:%s' % (src, line, pos)
489                        else:
490                            where = '%s:%s' % (src, line)
491                        if where not in archive:
492                            archive[where] = 1
493                        else:
494                            archive[where] += 1
495                        if build.key() not in self.messages[messages]:
496                            self.messages[messages][build.key()] = { }
497                        self.messages[messages][build.key()][where] = msg
498            finally:
499                self.lock.release()
500
501class results:
502
503    def __init__(self, source_base, groups):
504        self.lock = threading.Lock()
505        self.errors = { 'pass':      0,
506                        'configure': 0,
507                        'build':     0,
508                        'tests':     0,
509                        'passes':    { },
510                        'fails':     { } }
511        self.counts = { 'h'        : 0,
512                        'exes'     : 0,
513                        'objs'     : 0,
514                        'libs'     : 0 }
515        self.warnings_errors = warnings_errors(source_base, groups)
516
517    def _arch_bsp(self, arch, bsp):
518        return '%s/%s' % (arch, bsp)
519
520    def _arch_bsp_passes(self, build):
521        if build.key() not in self.errors['passes']:
522            self.errors['passes'][build.key()] = []
523        return self.errors['passes'][build.key()]
524
525    def _arch_bsp_fails(self, build):
526        if build.key() not in self.errors['fails']:
527            self.errors['fails'][build.key()] = []
528        return self.errors['fails'][build.key()]
529
530    def _count(self, label):
531        count = 0
532        for build in self.errors[label]:
533            count += len(self.errors[label][build])
534        return count
535
536    def _max_col(self, label):
537        max_col = 0
538        for build in self.errors[label]:
539            arch, bsp, build_config = arch_bsp_build_parse(build)
540            arch_bsp = self._arch_bsp(arch, bsp)
541            if len(arch_bsp) > max_col:
542                max_col = len(arch_bsp)
543        return max_col
544
545    def get_warning_count(self):
546        return self.warnings_errors.get_warning_count()
547
548    def get_error_count(self):
549        return self.warnings_errors.get_error_count()
550
551    def get_warning_messages(self, build):
552        return self.warnings_errors.get_warning_messages(build)
553
554    def get_error_messages(self, build):
555        return self.warnings_errors.get_error_messages(build)
556
557    def status(self):
558        self.lock.acquire()
559        try:
560            s = 'Pass: %4d  Fail: %4d (configure:%d build:%d)' % \
561                (self.errors['pass'],
562                 self.errors['configure'] + self.errors['build'],
563                 self.errors['configure'], self.errors['build'])
564        except:
565            raise
566        finally:
567            self.lock.release()
568        return s;
569
570    def add_fail(self, phase, build, configure, warnings, error_messages):
571        fails = self._arch_bsp_fails(build)
572        self.lock.acquire()
573        try:
574            self.errors[phase] += 1
575            fails += [(phase, build.build_config, configure, error_messages)]
576        finally:
577            self.lock.release()
578
579    def add_pass(self, build, configure, warnings):
580        passes = self._arch_bsp_passes(build)
581        self.lock.acquire()
582        try:
583            self.errors['pass'] += 1
584            passes += [(build.build_config, configure, warnings, None)]
585        finally:
586            self.lock.release()
587
588    def pass_count(self):
589        return self._count('passes')
590
591    def fail_count(self):
592        return self._count('fails')
593
594    def _failures_report(self, build, count):
595        if type(build) is str:
596            build_key = build
597        else:
598            build_key = build.key()
599        if build_key not in self.errors['fails'] or \
600           len(self.errors['fails'][build_key]) == 0:
601            return count, 0, ' No failures'
602        absize = 0
603        bsize = 0
604        ssize = 0
605        arch, bsp, build_set = arch_bsp_build_parse(build_key)
606        arch_bsp = self._arch_bsp(arch, bsp)
607        fails = self.errors['fails'][build_key]
608        for f in fails:
609            if len(f[0]) > ssize:
610                ssize = len(f[0])
611        s = ''
612        for f in fails:
613            count += 1
614            fcl = ' %3d' % (count)
615            state = f[0]
616            s += '%s %s %s %-*s:%s' % \
617                 (fcl, build_set, arch_bsp, ssize, state, os.linesep)
618            s1 = ' ' * 6
619            s += wrap((s1, 'configure: ' + f[2]), lineend = '\\', width = 75)
620            s1 = ' ' * 5
621            for e in self.warnings_errors.get_error_messages(build):
622                s += wrap([s1 + 'error: ', e])
623        return count, len(fails), s
624
625    def failures_report(self, build = None):
626        s = ''
627        count = 0
628        if build is not None:
629            count, build_fails, bs = self._failures_report(build, count)
630            if build_fails > 0:
631                s += bs + os.linesep
632        else:
633            self.lock.acquire()
634            builds = sorted(self.errors['fails'].keys())
635            self.lock.release()
636            for build in builds:
637                count, build_fails, bs = self._failures_report(build, count)
638                if build_fails > 0:
639                    s += bs + os.linesep
640        if count == 0:
641            s = ' No failures' + os.linesep
642        return s
643
644    def warnings_report(self, summary = False):
645        return self.warnings_errors.warnings_report(summary)
646
647    def report(self):
648        self.lock.acquire()
649        log_lock.acquire()
650        passes = self.pass_count()
651        fails = self.fail_count()
652        log.notice('Passes: %d   Failures: %d' % (passes, fails))
653        log.output()
654        log.output('Build Report')
655        log.output('   Passes: %d   Failures: %d' % (passes, fails))
656        log.output(' Failures:')
657        if fails == 0:
658            log.output('  None')
659        else:
660            max_col = self._max_col('fails')
661            for build in self.errors['fails']:
662                arch, bsp, build_config = arch_bsp_build_parse(build)
663                arch_bsp = self._arch_bsp(arch, bsp)
664                for f in self.errors['fails'][build]:
665                    config_cmd = f[2]
666                    config_at = config_cmd.find('configure')
667                    if config_at != -1:
668                        config_cmd = config_cmd[config_at:]
669                    log.output(' %*s:' % (max_col + 2, arch_bsp))
670                    s1 = ' ' * 6
671                    log.output(wrap([s1, config_cmd], lineend = '\\', width = 75))
672                    if f[3] is not None:
673                        s1 = ' ' * len(s1)
674                        for msg in f[3]:
675                            log.output(wrap([s1, msg], lineend = '\\'))
676        log.output(' Passes:')
677        if passes == 0:
678            log.output('  None')
679        else:
680            max_col = self._max_col('passes')
681            for build in self.errors['passes']:
682                arch, bsp, build_config = arch_bsp_build_parse(build)
683                arch_bsp = self._arch_bsp(arch, bsp)
684                for f in self.errors['passes'][build]:
685                    config_cmd = f[1]
686                    config_at = config_cmd.find('configure')
687                    if config_at != -1:
688                        config_cmd = config_cmd[config_at:]
689                    log.output(' %s (%5d):' % (arch_bsp, f[2]))
690                    log.output(wrap([' ' * 6, config_cmd], lineend = '\\', width = 75))
691        log_lock.release()
692        self.lock.release()
693
694class arch_bsp_builder:
695
696    def __init__(self, results_, build, commands, build_dir, tag):
697        self.lock = threading.Lock()
698        self.state = 'ready'
699        self.thread = None
700        self.proc = None
701        self.results = results_
702        self.build = build
703        self.commands = commands
704        self.build_dir = build_dir
705        self.tag = tag
706        self.output = output_worker(results_.warnings_errors, build)
707        self.counts = { 'h'        : 0,
708                        'exes'     : 0,
709                        'objs'     : 0,
710                        'libs'     : 0 }
711
712    def _notice(self, text):
713        global max_build_label
714        arch, bsp, build_set = arch_bsp_build_parse(self.build.key())
715        label = '%s/%s (%s)' % (arch, bsp, build_set)
716        log.notice('[%s] %-*s %s' % (self.tag, max_build_label, label, text))
717
718    def _build_dir(self):
719        return path.join(self.build_dir, self.build.key())
720
721    def _make_build_dir(self):
722        if not path.exists(self._build_dir()):
723            path.mkdir(self._build_dir())
724
725    def _count_files(self):
726        for root, dirs, files in os.walk(self._build_dir()):
727            for file in files:
728                if file.endswith('.exe'):
729                    self.counts['exes'] += 1
730                elif file.endswith('.o'):
731                    self.counts['objs'] += 1
732                elif file.endswith('.a'):
733                    self.counts['libs'] += 1
734                elif file.endswith('.h'):
735                    self.counts['h'] += 1
736
737    def _execute(self, phase):
738        exit_code = 0
739        cmd = self.commands[phase]
740        try:
741            # This should locked; not sure how to do that
742            self.proc = execute.capture_execution(log = self.output)
743            log.output(wrap(('run:', self.build.key(), cmd), lineend = '\\'))
744            if not self.commands['dry-run']:
745                exit_code, proc, output = self.proc.shell(cmd,
746                                                          cwd = path.host(self._build_dir()))
747        except:
748            traceback.print_exc()
749            self.lock.acquire()
750            if self.proc is not None:
751                self.proc.kill()
752            self.lock.release()
753            exit_code = 1
754        self.lock.acquire()
755        self.proc = None
756        self.lock.release()
757        return exit_code == 0
758
759    def _configure(self):
760        return self._execute('configure')
761
762    def _make(self):
763        return self._execute('build')
764
765    def _worker(self):
766        self.lock.acquire()
767        self.state = 'running'
768        self.lock.release()
769        self.build.start()
770        warnings = self.results.get_warning_count()
771        ok = False
772        try:
773            log_lock.acquire()
774            try:
775                self._notice('Start')
776                self._notice('Creating: %s' % (self._build_dir()))
777            except:
778                raise
779            finally:
780                log_lock.release()
781            self._make_build_dir()
782            self._notice('Configuring')
783            ok = self._configure()
784            if not ok:
785                warnings = self.results.get_warning_count() - warnings
786                self.results.add_fail('configure',
787                                      self.build,
788                                      self.commands['configure'],
789                                      warnings,
790                                      self.results.get_error_messages(self.build))
791            self.lock.acquire()
792            if self.state == 'killing':
793                ok = False
794            self.lock.release()
795            if ok:
796                self._notice('Building')
797                ok = self._make()
798                if not ok:
799                    warnings = self.results.get_warning_count() - warnings
800                    self.results.add_fail('build',
801                                          self.build,
802                                          self.commands['configure'],
803                                          warnings,
804                                          self.results.get_error_messages(self.build))
805            if ok:
806                warnings = self.results.get_warning_count() - warnings
807                self.results.add_pass(self.build,
808                                      self.commands['configure'],
809                                      warnings)
810        except:
811            ok = False
812            self._notice('Build Exception')
813            traceback.print_exc()
814        self.build.stop()
815        log_lock.acquire()
816        try:
817            self._count_files()
818            if ok:
819                self._notice('PASS')
820            else:
821                self._notice('FAIL')
822            self._notice('Warnings:%d  exes:%d  objs:%d  libs:%d' % \
823                         (warnings, self.counts['exes'],
824                          self.counts['objs'], self.counts['libs']))
825            log.output('  %s: Failure Report:' % (self.build.key()))
826            log.output(self.results.failures_report(self.build))
827            self._notice('Finished (duration:%s)' % (str(self.build.duration())))
828            self._notice('Status: %s' % (self.results.status()))
829        except:
830            self._notice('Build Exception:')
831            traceback.print_exc()
832        finally:
833            log_lock.release()
834        self.lock.acquire()
835        self.state = 'finished'
836        self.lock.release()
837
838    def get_file_counts(self):
839        return self.counts
840
841    def run(self):
842        self.lock.acquire()
843        try:
844            if self.state != 'ready':
845                raise error.general('builder already run')
846            self.state = 'starting'
847            self.thread = threading.Thread(target = self._worker)
848            self.thread.start()
849        except:
850            raise
851        finally:
852            self.lock.release()
853
854    def kill(self):
855        self.lock.acquire()
856        if self.thread is not None:
857            self.state = 'killing'
858            if self.proc is not None:
859                try:
860                    self.proc.kill()
861                except:
862                    pass
863            self.lock.release()
864            self.thread.join(5)
865            self.lock.acquire()
866        self.state = 'finished'
867        self.lock.release()
868
869    def current_state(self):
870        self.lock.acquire()
871        state = self.state
872        self.lock.release()
873        return state
874
875    def log_output(self):
876        self.output.log_output(['-' * 79, '] %s: Build output:' % (self.build.key())])
877
878    def clean(self):
879        if not self.commands['no-clean']:
880            self._notice('Cleaning: %s' % (self._build_dir()))
881            path.removeall(self._build_dir())
882
883class configuration_:
884
885    def __init__(self):
886        self.config = configuration.configuration()
887        self.archs = { }
888        self.builds_ = { }
889        self.profiles = { }
890
891    def __str__(self):
892        s = self.name + os.linesep
893        s += 'Archs:' + os.linesep + \
894             pprint.pformat(self.archs, indent = 1, width = 80) + os.linesep
895        s += 'Builds:' + os.linesep + \
896             pprint.pformat(self.builds_, indent = 1, width = 80) + os.linesep
897        s += 'Profiles:' + os.linesep + \
898             pprint.pformat(self.profiles, indent = 1, width = 80) + os.linesep
899        return s
900
901    def _build_options(self, build, nesting = 0):
902        if ':' in build:
903            section, name = build.split(':', 1)
904            opts = [self.config.get_item(section, name)]
905            return opts
906        builds = self.builds_['builds']
907        if build not in builds:
908            raise error.general('build %s not found' % (build))
909        if nesting > 20:
910            raise error.general('nesting build %s' % (build))
911        options = []
912        for option in self.builds_['builds'][build]:
913            if ':' in option:
914                section, name = option.split(':', 1)
915                opts = [self.config.get_item(section, name)]
916            else:
917                opts = self._build_options(option, nesting + 1)
918            for opt in opts:
919                if opt not in options:
920                    options += [opt]
921        return options
922
923    def load(self, name, build):
924        self.config.load(name)
925        archs = []
926        self.profiles['profiles'] = \
927            self.config.comma_list('profiles', 'profiles', err = False)
928        if len(self.profiles['profiles']) == 0:
929            self.profiles['profiles'] = ['tier-%d' % (t) for t in range(1,4)]
930        for p in self.profiles['profiles']:
931            profile = {}
932            profile['name'] = p
933            profile['archs'] = self.config.comma_list(profile['name'], 'archs', err = False)
934            archs += profile['archs']
935            for arch in profile['archs']:
936                bsps = 'bsps_%s' % (arch)
937                profile[bsps] = self.config.comma_list(profile['name'], bsps)
938            self.profiles[profile['name']] = profile
939        invalid_chars = re.compile(r'[^a-zA-Z0-9_-]')
940        for a in set(archs):
941            if len(invalid_chars.findall(a)) != 0:
942                raise error.general('invalid character(s) in arch name: %s' % (a))
943            arch = {}
944            arch['excludes'] = {}
945            for exclude in self.config.comma_list(a, 'exclude', err = False):
946                arch['excludes'][exclude] = ['all']
947            for i in self.config.get_items(a, False):
948                if i[0].startswith('exclude-'):
949                    exclude = i[0][len('exclude-'):]
950                    if exclude not in arch['excludes']:
951                        arch['excludes'][exclude] = []
952                    arch['excludes'][exclude] += \
953                        sorted(set([b.strip() for b in i[1].split(',')]))
954            arch['bsps'] = self.config.comma_list(a, 'bsps', err = False)
955            for b in arch['bsps']:
956                if len(invalid_chars.findall(b)) != 0:
957                    raise error.general('invalid character(s) in BSP name: %s' % (b))
958                arch[b] = {}
959                arch[b]['bspopts'] = \
960                    self.config.comma_list(a, 'bspopts_%s' % (b), err = False)
961            self.archs[a] = arch
962        builds = {}
963        builds['default'] = self.config.get_item('builds', 'default')
964        if build is None:
965            build = builds['default']
966        builds['config'] = { }
967        for config in self.config.get_items('config'):
968            builds['config'][config[0]] = config[1]
969        builds['build'] = build
970        builds_ = self.config.get_item_names('builds')
971        builds['builds'] = {}
972        for build in builds_:
973            build_builds = self.config.comma_list('builds', build)
974            has_config = False
975            has_build = False
976            for b in build_builds:
977                if ':' in b:
978                    if has_build:
979                        raise error.general('config and build in build: %s' % (build))
980                    has_config = True
981                else:
982                    if has_config:
983                        raise error.general('config and build in build: %s' % (build))
984                    has_build = True
985            builds['builds'][build] = build_builds
986        self.builds_ = builds
987
988    def build(self):
989        return self.builds_['build']
990
991    def builds(self):
992        if self.builds_['build'] in self.builds_['builds']:
993            build = copy.copy(self.builds_['builds'][self.builds_['build']])
994            if ':' in build[0]:
995                return [self.builds_['build']]
996            return build
997        return None
998
999    def build_options(self, build):
1000        return ' '.join(self._build_options(build))
1001
1002    def excludes(self, arch):
1003        excludes = self.archs[arch]['excludes'].keys()
1004        for exclude in self.archs[arch]['excludes']:
1005            if 'all' not in self.archs[arch]['excludes'][exclude]:
1006                excludes.remove(exclude)
1007        return sorted(excludes)
1008
1009    def archs(self):
1010        return sorted(self.archs.keys())
1011
1012    def arch_present(self, arch):
1013        return arch in self.archs
1014
1015    def arch_bsps(self, arch):
1016        return sorted(self.archs[arch]['bsps'])
1017
1018    def bsp_present(self, arch, bsp):
1019        return bsp in self.archs[arch]['bsps']
1020
1021    def bsp_excludes(self, arch, bsp):
1022        excludes = self.archs[arch]['excludes'].keys()
1023        for exclude in self.archs[arch]['excludes']:
1024            if 'all' not in self.archs[arch]['excludes'][exclude] and \
1025               bsp not in self.archs[arch]['excludes'][exclude]:
1026                excludes.remove(exclude)
1027        return sorted(excludes)
1028
1029    def bspopts(self, arch, bsp):
1030        if arch not in self.archs:
1031            raise error.general('invalid architecture: %s' % (arch))
1032        if bsp not in self.archs[arch]:
1033            raise error.general('invalid BSP: %s' % (bsp))
1034        return self.archs[arch][bsp]['bspopts']
1035
1036    def profile_present(self, profile):
1037        return profile in self.profiles
1038
1039    def profile_archs(self, profile):
1040        if profile not in self.profiles:
1041            raise error.general('invalid profile: %s' % (profile))
1042        return self.profiles[profile]['archs']
1043
1044    def profile_arch_bsps(self, profile, arch):
1045        if profile not in self.profiles:
1046            raise error.general('invalid profile: %s' % (profile))
1047        if 'bsps_%s' % (arch) not in self.profiles[profile]:
1048            raise error.general('invalid profile arch: %s' % (arch))
1049        return ['%s/%s' % (arch, bsp) for bsp in self.profiles[profile]['bsps_%s' % (arch)]]
1050
1051    def report(self, profiles = True, builds = True, architectures = True):
1052        width = 70
1053        cols_1 = [width]
1054        cols_2 = [10, width - 10]
1055        s = textbox.line(cols_1, line = '=', marker = '+', indent = 1)
1056        s1 = ' File(s)'
1057        for f in self.config.files():
1058            colon = ':'
1059            for l in textwrap.wrap(f, width = cols_2[1] - 3):
1060                s += textbox.row(cols_2, [s1, ' ' + l], marker = colon, indent = 1)
1061                colon = ' '
1062                s1 = ' ' * len(s1)
1063        s += textbox.line(cols_1, marker = '+', indent = 1)
1064        s += os.linesep
1065        if profiles:
1066            s += textbox.line(cols_1, line = '=', marker = '+', indent = 1)
1067            profiles = sorted(self.profiles['profiles'])
1068            archs = []
1069            bsps = []
1070            for profile in profiles:
1071                archs += self.profiles[profile]['archs']
1072                for arch in sorted(self.profiles[profile]['archs']):
1073                    bsps += self.profiles[profile]['bsps_%s' % (arch)]
1074            archs = len(set(archs))
1075            bsps = len(set(bsps))
1076            s += textbox.row(cols_1,
1077                             [' Profiles : %d (archs:%d, bsps:%d)' % \
1078                              (len(profiles), archs, bsps)],
1079                             indent = 1)
1080            for profile in profiles:
1081                textbox.row(cols_2,
1082                            [profile, self.profiles[profile]['name']],
1083                            indent = 1)
1084            s += textbox.line(cols_1, marker = '+', indent = 1)
1085            for profile in profiles:
1086                s += textbox.row(cols_1, [' %s' % (profile)], indent = 1)
1087                profile = self.profiles[profile]
1088                archs = sorted(profile['archs'])
1089                for arch in archs:
1090                    arch_bsps = ', '.join(profile['bsps_%s' % (arch)])
1091                    if len(arch_bsps) > 0:
1092                        s += textbox.line(cols_2, marker = '+', indent = 1)
1093                        s1 = ' ' + arch
1094                        for l in textwrap.wrap(arch_bsps,
1095                                               width = cols_2[1] - 3):
1096                            s += textbox.row(cols_2, [s1, ' ' + l], indent = 1)
1097                            s1 = ' ' * len(s1)
1098                s += textbox.line(cols_2, marker = '+', indent = 1)
1099            s += os.linesep
1100        if builds:
1101            s += textbox.line(cols_1, line = '=', marker = '+', indent = 1)
1102            s += textbox.row(cols_1,
1103                             [' Builds:  %s (default)' % (self.builds_['default'])],
1104                             indent = 1)
1105            builds = self.builds_['builds']
1106            bsize = 0
1107            for build in builds:
1108                if len(build) > bsize:
1109                    bsize = len(build)
1110            cols_b = [bsize + 2, width - bsize - 2]
1111            s += textbox.line(cols_b, marker = '+', indent = 1)
1112            for build in builds:
1113                s1 = ' ' + build
1114                for l in textwrap.wrap(', '.join(builds[build]),
1115                                       width = cols_b[1] - 3):
1116                    s += textbox.row(cols_b, [s1, ' ' + l], indent = 1)
1117                    s1 = ' ' * len(s1)
1118                s += textbox.line(cols_b, marker = '+', indent = 1)
1119            configs = self.builds_['config']
1120            s += textbox.row(cols_1,
1121                             [' Configure Options: %d' % (len(configs))],
1122                             indent = 1)
1123            csize = 0
1124            for config in configs:
1125                if len(config) > csize:
1126                    csize = len(config)
1127            cols_c = [csize + 3, width - csize - 3]
1128            s += textbox.line(cols_c, marker = '+', indent = 1)
1129            for config in configs:
1130                s1 = ' ' + config
1131                for l in textwrap.wrap(configs[config], width = cols_c[1] - 3):
1132                    s += textbox.row(cols_c, [s1, ' ' + l], indent = 1)
1133                    s1 = ' ' * len(s1)
1134                s += textbox.line(cols_c, marker = '+', indent = 1)
1135            s += os.linesep
1136        if architectures:
1137            s += textbox.line(cols_1, line = '=', marker = '+', indent = 1)
1138            archs = sorted(self.archs.keys())
1139            bsps = 0
1140            asize = 0
1141            for arch in archs:
1142                if len(arch) > asize:
1143                    asize = len(arch)
1144                bsps += len(self.archs[arch]['bsps'])
1145            s += textbox.row(cols_1,
1146                             [' Architectures : %d (bsps: %d)' % (len(archs), bsps)],
1147                             indent = 1)
1148            cols_a = [asize + 2, width - asize - 2]
1149            s += textbox.line(cols_a, marker = '+', indent = 1)
1150            for arch in archs:
1151                s += textbox.row(cols_a,
1152                                 [' ' + arch, ' %d' % (len(self.archs[arch]['bsps']))],
1153                                 indent = 1)
1154            s += textbox.line(cols_a, marker = '+', indent = 1)
1155            for archn in archs:
1156                arch = self.archs[archn]
1157                if len(arch['bsps']) > 0:
1158                    bsize = 0
1159                    for bsp in arch['bsps']:
1160                        if len(bsp) > bsize:
1161                            bsize = len(bsp)
1162                    cols_b = [bsize + 3, width - bsize - 3]
1163                    s += textbox.row(cols_1, [' ' + archn + ':'], indent = 1)
1164                    s += textbox.line(cols_b, marker = '+', indent = 1)
1165                    for bsp in arch['bsps']:
1166                        s1 = ' ' + bsp
1167                        bspopts = ' '.join(arch[bsp]['bspopts'])
1168                        if len(bspopts):
1169                            for l in textwrap.wrap('bopt: ' + bspopts,
1170                                                   width = cols_b[1] - 3):
1171                                s += textbox.row(cols_b, [s1, ' ' + l], indent = 1)
1172                                s1 = ' ' * len(s1)
1173                        excludes = []
1174                        for exclude in arch['excludes']:
1175                            if 'all' in arch['excludes'][exclude] or \
1176                               bsp in arch['excludes'][exclude]:
1177                                excludes += [exclude]
1178                        excludes = ', '.join(excludes)
1179                        if len(excludes):
1180                            for l in textwrap.wrap('ex: ' + excludes,
1181                                                   width = cols_b[1] - 3):
1182                                s += textbox.row(cols_b, [s1, ' ' + l], indent = 1)
1183                                s1 = ' ' * len(s1)
1184                        if len(bspopts) == 0 and len(excludes) == 0:
1185                            s += textbox.row(cols_b, [s1, ' '], indent = 1)
1186                    s += textbox.line(cols_b, marker = '+', indent = 1)
1187            s += os.linesep
1188        return s
1189
1190class build_jobs:
1191
1192    def __init__(self, config, arch, bsp):
1193        self.arch = arch
1194        self.bsp = bsp
1195        self.builds = config.builds()
1196        if self.builds is None:
1197            raise error.general('build not found: %s' % (config.build()))
1198        excludes = list(set(config.excludes(self.arch) +
1199                            config.bsp_excludes(self.arch, self.bsp)))
1200        #
1201        # The build can be in the buld string delimited by '-'.
1202        #
1203        remove = []
1204        for e in excludes:
1205            remove += [b for b in self.builds if e in b]
1206        self.builds = [b for b in self.builds if b not in remove]
1207        self.build_set = { }
1208        for build in self.builds:
1209            self.build_set[build] = config.build_options(build)
1210
1211    def jobs(self):
1212        return [arch_bsp_build(self.arch, self.bsp, b, self.build_set[b]) \
1213                for b in sorted(self.build_set.keys())]
1214
1215class builder:
1216
1217    def __init__(self, config, version, prefix, tools, rtems, build_dir, options):
1218        self.config = config
1219        self.build_dir = build_dir
1220        self.rtems_version = version
1221        self.prefix = prefix
1222        self.tools = tools
1223        self.rtems = rtems
1224        self.options = options
1225        self.counts = { 'h'        : 0,
1226                        'exes'     : 0,
1227                        'objs'     : 0,
1228                        'libs'     : 0 }
1229        self.results = results(rtems,
1230                               { 'groups'  : ['Shared', 'BSP', 'Network', 'Tests',
1231                                              'LibCPU', 'CPU Kit'],
1232                                 'exclude' : '.*Makefile.*',
1233                                 'CPU Kit' : '.*cpukit/.*',
1234                                 'Network' : '.*libnetworking/.*|.*librpc/.*',
1235                                 'Tests'   : '.*testsuites/.*',
1236                                 'BSP'     : '.*libbsp/.*',
1237                                 'LibCPU'  : '.*libcpu/.*',
1238                                 'Shared'  : '.*shared/.*' })
1239        if not path.exists(path.join(rtems, 'configure')) or \
1240           not path.exists(path.join(rtems, 'Makefile.in')) or \
1241           not path.exists(path.join(rtems, 'cpukit')):
1242            raise error.general('RTEMS source path does not look like RTEMS')
1243
1244    def _bsps(self, arch):
1245        return self.config.arch_bsps(arch)
1246
1247    def _create_build_jobs(self, jobs, build_job_count):
1248        max_job_size = len('%d' % len(jobs))
1249        build_jobs = []
1250        job_index = 1
1251        for job in jobs:
1252            tag = '%*d/%d' % (max_job_size, job_index, len(jobs))
1253            build_jobs += [arch_bsp_builder(self.results,
1254                                            job,
1255                                            self._commands(job, build_job_count),
1256                                            self.build_dir,
1257                                            tag)]
1258            job_index += 1
1259        set_max_build_label(build_jobs)
1260        return build_jobs
1261
1262    def _commands(self, build, build_jobs):
1263        commands = { 'dry-run'  : self.options['dry-run'],
1264                     'no-clean' : self.options['no-clean'],
1265                     'configure': None,
1266                     'build'    : None }
1267        cmds = build.build_config.split()
1268        cmds += self.config.bspopts(build.arch, build.bsp)
1269        cmd = [path.join(self.rtems, 'configure')]
1270        for c in cmds:
1271            c = c.replace('@PREFIX@', self.prefix)
1272            c = c.replace('@RTEMS_VERSION@', self.rtems_version)
1273            c = c.replace('@ARCH@', build.arch)
1274            c = c.replace('@BSP@', build.bsp)
1275            cmd += [c]
1276        commands['configure'] = ' '.join(cmd)
1277        cmd = 'make -j %s' % (build_jobs)
1278        commands['build'] = cmd
1279        return commands
1280
1281    def _update_file_counts(self, counts):
1282        for f in self.counts:
1283            if f in counts:
1284                self.counts[f] += counts[f]
1285        return counts
1286
1287    def _warnings_report(self):
1288        if self.options['warnings-report'] is not None:
1289            with open(self.options['warnings-report'], 'w') as f:
1290                f.write(title() + os.linesep)
1291                f.write(os.linesep)
1292                f.write('Date: %s%s' % (datetime.datetime.now().strftime('%c'),
1293                                        os.linesep))
1294                f.write(os.linesep)
1295                f.write(command_line() + os.linesep)
1296                f.write(self.results.warnings_errors.warnings_report())
1297
1298    def _failures_report(self):
1299        if self.options['failures-report'] is not None:
1300            with open(self.options['failures-report'], 'w') as f:
1301                f.write(title() + os.linesep)
1302                f.write(os.linesep)
1303                f.write('Date: %s%s' % (datetime.datetime.now().strftime('%c'),
1304                                        os.linesep))
1305                f.write(os.linesep)
1306                f.write(command_line() + os.linesep)
1307                f.write(self.results.failures_report())
1308
1309    def _finished(self):
1310        log.notice('Total: Warnings:%d  exes:%d  objs:%d  libs:%d' % \
1311                   (self.results.get_warning_count(), self.counts['exes'],
1312                    self.counts['objs'], self.counts['libs']))
1313        log.output()
1314        log.output('Warnings:')
1315        log.output(self.results.warnings_report())
1316        log.output()
1317        log.notice('Failures:')
1318        log.notice(self.results.failures_report())
1319        self._warnings_report()
1320        self._failures_report()
1321
1322    def run_jobs(self, jobs):
1323        if path.exists(self.build_dir):
1324            log.notice('Cleaning: %s' % (self.build_dir))
1325            path.removeall(self.build_dir)
1326        self.start = datetime.datetime.now()
1327        self.end = datetime.datetime.now()
1328        self.duration = self.end - self.start
1329        self.average = self.duration
1330        env_path = os.environ['PATH']
1331        os.environ['PATH'] = path.host(path.join(self.tools, 'bin')) + \
1332                             os.pathsep + os.environ['PATH']
1333        job_count, build_job_count = jobs_option_parse(self.options['jobs'])
1334        builds = self._create_build_jobs(jobs, build_job_count)
1335        active_jobs = []
1336        self.jobs_completed = 0
1337        try:
1338            while len(builds) > 0 or len(active_jobs) > 0:
1339                new_jobs = job_count - len(active_jobs)
1340                if new_jobs > 0:
1341                    active_jobs += builds[:new_jobs]
1342                    builds = builds[new_jobs:]
1343                finished_jobs = []
1344                for job in active_jobs:
1345                    state = job.current_state()
1346                    if state == 'ready':
1347                        job.run()
1348                    elif state != 'running':
1349                        finished_jobs += [job]
1350                for job in finished_jobs:
1351                    self._update_file_counts(job.get_file_counts())
1352                    job.log_output()
1353                    job.clean()
1354                    active_jobs.remove(job)
1355                    self.jobs_completed += 1
1356                time.sleep(0.250)
1357        except:
1358            for job in active_jobs:
1359                try:
1360                    job.kill()
1361                except:
1362                    pass
1363            raise
1364        self.end = datetime.datetime.now()
1365        os.environ['PATH'] = env_path
1366        self.duration = self.end - self.start
1367        if self.jobs_completed == 0:
1368            self.jobs_completed = 1
1369        self._finished()
1370        self.average = self.duration / self.jobs_completed
1371        log.notice('Average BSP Build Time: %s' % (str(self.average)))
1372        log.notice('Total Time %s' % (str(self.duration)))
1373
1374    def arch_bsp_jobs(self, arch, bsps):
1375        jobs = []
1376        for bsp in bsps:
1377            jobs += build_jobs(self.config, arch, bsp).jobs()
1378        return jobs
1379
1380    def bsp_jobs(self, bsps):
1381        jobs = []
1382        for bsp in bsps:
1383            if bsp.count('/') != 1:
1384                raise error.general('invalid bsp format (use: arch/bsp): %s' % (bsp))
1385            arch, bsp = bsp.split('/')
1386            jobs += build_jobs(self.config, arch, bsp).jobs()
1387        return jobs
1388
1389    def arch_jobs(self, archs):
1390        jobs = []
1391        for arch in archs:
1392            if not self.config.arch_present(arch):
1393                raise error.general('Architecture not found: %s' % (arch))
1394            jobs += self.arch_bsp_jobs(arch, self._bsps(arch))
1395        return jobs
1396
1397    def profile_jobs(self, profiles):
1398        jobs = []
1399        for profile in profiles:
1400            if not self.config.profile_present(profile):
1401                raise error.general('Profile not found: %s' % (profile))
1402            for arch in self.config.profile_archs(profile):
1403                jobs += self.bsp_jobs(self.config.profile_arch_bsps(profile, arch))
1404        return jobs
1405
1406    def build_bsps(self, bsps):
1407        log.notice('BSPS(s): %s' % (', '.join(bsps)))
1408        self.run_jobs(self.bsp_jobs(bsps))
1409
1410    def build_archs(self, archs):
1411        log.notice('Architecture(s): %s' % (', '.join(archs)))
1412        self.run_jobs(self.arch_jobs(archs))
1413
1414    def build_profiles(self, profiles):
1415        log.notice('Profile(s): %s' % (', '.join(profiles)))
1416        self.run_jobs(self.profile_jobs(profiles))
1417
1418def run_args(args):
1419    b = None
1420    ec = 0
1421    try:
1422        #
1423        # On Windows MSYS2 prepends a path to itself to the environment
1424        # path. This means the RTEMS specific automake is not found and which
1425        # breaks the bootstrap. We need to remove the prepended path. Also
1426        # remove any ACLOCAL paths from the environment.
1427        #
1428        if os.name == 'nt':
1429            cspath = os.environ['PATH'].split(os.pathsep)
1430            if 'msys' in cspath[0] and cspath[0].endswith('bin'):
1431                os.environ['PATH'] = os.pathsep.join(cspath[1:])
1432
1433        start = datetime.datetime.now()
1434        top = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0])))
1435        prefix = '/opt/rtems/%s' % (rtems_version())
1436        tools = prefix
1437        build_dir = 'bsp-builds'
1438        logf = 'bsp-build-%s.txt' % \
1439               (datetime.datetime.now().strftime('%Y%m%d-%H%M%S'))
1440        config_file = path.join(top, 'share', 'rtems', 'tester',
1441                                'rtems', 'rtems-bsps.ini')
1442        if not path.exists(config_file):
1443            config_file = path.join(top, 'tester', 'rtems', 'rtems-bsps.ini')
1444
1445        argsp = argparse.ArgumentParser()
1446        argsp.add_argument('--prefix', help = 'Prefix to build the BSP.',
1447                           type = str)
1448        argsp.add_argument('--rtems-tools', help = 'The RTEMS tools directory.',
1449                           type = str)
1450        argsp.add_argument('--rtems', help = 'The RTEMS source tree.',
1451                           type = str)
1452        argsp.add_argument('--build-path', help = 'Path to build in.',
1453                           type = str)
1454        argsp.add_argument('--log', help = 'Log file.', type = str)
1455        argsp.add_argument('--config-report', help = 'Report the configuration.',
1456                           type = str, default = None,
1457                           choices = ['all', 'profiles', 'builds', 'archs'])
1458        argsp.add_argument('--warnings-report', help = 'Report the warnings to a file.',
1459                           type = str, default = None)
1460        argsp.add_argument('--failures-report', help = 'Report the failures to a file.',
1461                           type = str, default = None)
1462        argsp.add_argument('--stop-on-error', help = 'Stop on an error.',
1463                           action = 'store_true')
1464        argsp.add_argument('--no-clean', help = 'Do not clean the build output.',
1465                           action = 'store_true')
1466        argsp.add_argument('--profiles', help = 'Build the listed profiles (profile,profile,..).',
1467                           type = str, default = 'tier-1')
1468        argsp.add_argument('--arch', help = 'Build the architectures (arch,arch,..).',
1469                           type = str)
1470        argsp.add_argument('--bsp', help = 'Build the BSPs (arch/bsp,arch/bsp,..).',
1471                           type = str)
1472        argsp.add_argument('--build', help = 'Build name to build (see --config-report).',
1473                           type = str, default='all')
1474        argsp.add_argument('--jobs', help = 'Number of jobs to run.',
1475                           type = str, default = '1/%d' % (host.cpus()))
1476        argsp.add_argument('--dry-run', help = 'Do not run the actual builds.',
1477                           action = 'store_true')
1478        mailer.add_arguments(argsp)
1479
1480        opts = argsp.parse_args(args[1:])
1481        mail = None
1482        if opts.mail:
1483            mail = mailer.mail(opts)
1484            # Request these now to generate any errors.
1485            from_addr = mail.from_address()
1486            smtp_host = mail.smtp_host()
1487            if 'mail_to' in opts and opts.mail_to is not None:
1488                to_addr = opts.mail_to
1489            else:
1490                to_addr = 'build@rtems.org'
1491        if opts.log is not None:
1492            logf = opts.log
1493        log.default = log.log([logf])
1494        log.notice(title())
1495        log.output(command_line())
1496        if mail:
1497            log.notice('Mail: from:%s to:%s smtp:%s' % (from_addr,
1498                                                        to_addr,
1499                                                        smtp_host))
1500
1501        config = configuration_()
1502        config.load(config_file, opts.build)
1503
1504        if opts.config_report:
1505            log.notice('Configuration Report: %s' % (opts.config_report))
1506            c_profiles = False
1507            c_builds = False
1508            c_archs = False
1509            if opts.config_report == 'all':
1510                c_profiles = True
1511                c_builds = True
1512                c_archs = True
1513            elif opts.config_report == 'profiles':
1514                c_profiles = True
1515            elif opts.config_report == 'builds':
1516                c_builds = True
1517            elif opts.config_report == 'archs':
1518                c_archs = True
1519            log.notice(config.report(c_profiles, c_builds, c_archs))
1520            sys.exit(0)
1521
1522        if opts.rtems is None:
1523            raise error.general('No RTEMS source provided on the command line')
1524        if opts.prefix is not None:
1525            prefix = path.shell(opts.prefix)
1526        if opts.rtems_tools is not None:
1527            tools = path.shell(opts.rtems_tools)
1528        if opts.build_path is not None:
1529            build_dir = path.shell(opts.build_path)
1530
1531        options = { 'stop-on-error'   : opts.stop_on_error,
1532                    'no-clean'        : opts.no_clean,
1533                    'dry-run'         : opts.dry_run,
1534                    'jobs'            : opts.jobs,
1535                    'warnings-report' : opts.warnings_report,
1536                    'failures-report' : opts.failures_report }
1537
1538        b = builder(config, rtems_version(), prefix, tools,
1539                    path.shell(opts.rtems), build_dir, options)
1540
1541        profiles = comma_split(opts.profiles)
1542        archs = comma_split(opts.arch)
1543        bsps = comma_split(opts.bsp)
1544
1545        #
1546        # The default is build a profile.
1547        #
1548        if bsps is not None:
1549            if archs is not None:
1550                raise error.general('--arch supplied with --bsp;' \
1551                                    ' use --bsp=arch/bsp,arch/bsp,..')
1552            what = 'BSPs: %s' % (' '.join(bsps))
1553            b.build_bsps(bsps)
1554        elif archs is not None:
1555            what = 'Archs: %s' % (' '.join(archs))
1556            b.build_archs(archs)
1557        else:
1558            what = 'Profile(s): %s' % (' '.join(profiles))
1559            b.build_profiles(profiles)
1560        end = datetime.datetime.now()
1561
1562        #
1563        # Email the results of the build.
1564        #
1565        if mail is not None:
1566            subject = '[rtems-bsp-builder] %s: %s' % (str(start).split('.')[0],
1567                                                      what)
1568            t = title()
1569            body = t + os.linesep
1570            body += '=' * len(t) + os.linesep
1571            body += os.linesep
1572            body += 'Host: %s' % (os.uname()[3]) + os.linesep
1573            body += os.linesep
1574            body += command_line()
1575            body += os.linesep
1576            body += 'Total Time            : %s for %d completed job(s)' % \
1577                    (str(b.duration), b.jobs_completed)
1578            body += os.linesep
1579            body += 'Average BSP Build Time: %s' % (str(b.average))
1580            body += os.linesep + os.linesep
1581            body += 'Builds' + os.linesep
1582            body += '======' + os.linesep
1583            body += os.linesep.join([' ' + cb for cb in config.builds()])
1584            body += os.linesep + os.linesep
1585            body += 'Failures Report' + os.linesep
1586            body += '===============' + os.linesep
1587            body += b.results.failures_report()
1588            body += os.linesep
1589            body += 'Warnings Report' + os.linesep
1590            body += '===============' + os.linesep
1591            body += b.results.warnings_report(summary = True)
1592            mail.send(to_addr, subject, body)
1593
1594    except error.general as gerr:
1595        print(gerr)
1596        print('BSP Build FAILED', file = sys.stderr)
1597        ec = 1
1598    except error.internal as ierr:
1599        print(ierr)
1600        print('BSP Build FAILED', file = sys.stderr)
1601        ec = 1
1602    except error.exit as eerr:
1603        pass
1604    except KeyboardInterrupt:
1605        log.notice('abort: user terminated')
1606        ec = 1
1607    if b is not None:
1608        b.results.report()
1609    sys.exit(ec)
1610
1611def run():
1612    run_args(sys.argv)
1613
1614if __name__ == "__main__":
1615    run()
Note: See TracBrowser for help on using the repository browser.