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

Last change on this file since 37a0843 was 37a0843, checked in by Chris Johns <chrisj@…>, on May 24, 2017 at 2:27:38 AM

rtems-bsp-builder: Refactor to add jobs.

This change adds job support to the rtems-bsp-builder so builds can
run in parallel.

Some options have changed so they make sense and are more useful.

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