source: rtems-source-builder/source-builder/sb/setbuilder.py @ 4bd058e

4.104.114.95
Last change on this file since 4bd058e was 4bd058e, checked in by Chris Johns <chrisj@…>, on 05/22/15 at 02:20:41

sb: Always generate an XML report.

  • Property mode set to 100644
File size: 20.0 KB
RevLine 
[bf13d27]1#
2# RTEMS Tools Project (http://www.rtems.org/)
[2f72d35]3# Copyright 2010-2013 Chris Johns (chrisj@rtems.org)
[bf13d27]4# All rights reserved.
5#
6# This file is part of the RTEMS Tools package in 'rtems-tools'.
7#
8# Permission to use, copy, modify, and/or distribute this software for any
9# purpose with or without fee is hereby granted, provided that the above
10# copyright notice and this permission notice appear in all copies.
11#
12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#
[2f72d35]21# This code builds a package compiler tool suite given a tool set. A tool
[bf13d27]22# set lists the various tools. These are specific tool configurations.
23#
24
[984e4e6]25import copy
[251a42d]26import datetime
[71b8893]27import glob
[bf13d27]28import operator
29import os
[06834cf]30import sys
[bf13d27]31
[c18c4b6]32try:
33    import build
34    import check
35    import error
36    import log
[97a685f]37    import mailer
[cb12e48]38    import options
[c18c4b6]39    import path
40    import reports
[9a15c40]41    import sources
[affea81]42    import version
[c18c4b6]43except KeyboardInterrupt:
[06834cf]44    print 'abort: user terminated'
[c18c4b6]45    sys.exit(1)
46except:
[06834cf]47    print 'error: unknown application load error'
[c18c4b6]48    sys.exit(1)
[bf13d27]49
[ab8319a]50class buildset:
51    """Build a set builds a set of packages."""
[bf13d27]52
[cb12e48]53    def __init__(self, bset, _configs, opts, macros = None):
[5142bec]54        log.trace('_bset: %s: init' % (bset))
[fba1136]55        self.configs = _configs
[984e4e6]56        self.opts = opts
[cb12e48]57        if macros is None:
58            self.macros = copy.copy(opts.defaults)
59        else:
60            self.macros = copy.copy(macros)
[ab8319a]61        self.bset = bset
[ce0f7a1]62        _target = self.macros.expand('%{_target}')
63        if len(_target):
64            pkg_prefix = _target
65        else:
66            pkg_prefix = self.macros.expand('%{_host}')
67        self.bset_pkg = '%s-%s-set' % (pkg_prefix, self.bset)
[c914e1d]68        self.mail_header = ''
[97a685f]69        self.mail_report = ''
[df56f7e]70        self.build_failure = None
[c914e1d]71
72    def write_mail_header(self, text, prepend = False):
73        if len(text) == 0 or text[-1] != '\n' or text[-1] != '\r':
74            text += os.linesep
75        if prepend:
76            self.mail_header = text + self.mail_header
77        else:
78            self.mail_header += text
[adf0946]79
[97a685f]80    def write_mail_report(self, text, prepend = False):
[adf0946]81        if len(text) == 0 or text[-1] != '\n' or text[-1] != '\r':
82            text += os.linesep
83        if prepend:
[97a685f]84            self.mail_report = text + self.mail_report
[adf0946]85        else:
[97a685f]86            self.mail_report += text
[bf13d27]87
88    def copy(self, src, dst):
[01b2815]89        log.output('copy: %s => %s' % (path.host(src), path.host(dst)))
90        if not self.opts.dry_run():
[869b8a6]91            path.copy_tree(src, dst)
[bf13d27]92
[4bd058e]93    def report(self, _config, _build, opts, macros, format = None):
[ce60578]94        if len(_build.main_package().name()) > 0 \
[ebf8a1f]95           and not _build.macros.get('%{_disable_reporting}') \
[ce60578]96           and (not _build.opts.get_arg('--no-report') \
97                or _build.opts.get_arg('--mail')):
[4bd058e]98            if format is None:
99                format = _build.opts.get_arg('--report-format')
100                if format is not None:
101                    if len(format) != 2:
102                        raise error.general('invalid report format option: %s' % ('='.join(format)))
103                    format = format[1]
[26595c7]104            if format is None:
[ce60578]105                format = 'text'
[4bd058e]106            if format == 'text':
107                ext = '.txt'
108            elif format == 'asciidoc':
[ce60578]109                ext = '.txt'
[4bd058e]110            elif format == 'html':
111                ext = '.html'
112            elif format == 'xml':
113                ext = '.xml'
114            elif format == 'ini':
115                ext = '.ini'
[26595c7]116            else:
[4bd058e]117                raise error.general('invalid report format: %s' % (format))
[864e8ff]118            buildroot = _build.config.abspath('%{buildroot}')
[cb12e48]119            prefix = _build.macros.expand('%{_prefix}')
[06834cf]120            name = _build.main_package().name() + ext
[5142bec]121            log.notice('reporting: %s -> %s' % (_config, name))
[adf0946]122            if not _build.opts.get_arg('--no-report'):
[ce60578]123                outpath = path.host(path.join(buildroot, prefix, 'share', 'rtems', 'rsb'))
124                if not _build.opts.dry_run():
125                    outname = path.host(path.join(outpath, name))
126                else:
127                    outname = None
128                r = reports.report(format, self.configs,
129                                   copy.copy(opts), copy.copy(macros))
[adf0946]130                r.introduction(_build.config.file_name())
[ce60578]131                r.generate(_build.config.file_name())
132                r.epilogue(_build.config.file_name())
[adf0946]133                if not _build.opts.dry_run():
134                    _build.mkdir(outpath)
135                    r.write(outname)
136                del r
[ce60578]137            if _build.opts.get_arg('--mail'):
138                r = reports.report('text', self.configs,
139                                   copy.copy(opts), copy.copy(macros))
[adf0946]140                r.introduction(_build.config.file_name())
[ce60578]141                r.generate(_build.config.file_name())
142                r.epilogue(_build.config.file_name())
[c914e1d]143                self.write_mail_report(r.out)
[26595c7]144                del r
145
[4f26bdb]146    def root_copy(self, src, dst):
147        what = '%s -> %s' % \
148            (os.path.relpath(path.host(src)), os.path.relpath(path.host(dst)))
[5142bec]149        log.trace('_bset: %s: collecting: %s' % (self.bset, what))
[04f447f]150        self.copy(src, dst)
[bf13d27]151
[4f26bdb]152    def install(self, name, buildroot, prefix):
153        dst = prefix
154        src = path.join(buildroot, prefix)
[5142bec]155        log.notice('installing: %s -> %s' % (name, path.host(dst)))
[04f447f]156        self.copy(src, dst)
[4f26bdb]157
158    def canadian_cross(self, _build):
[cb12e48]159        # @fixme Switch to using a private macros map.
160        macros_to_save = ['%{_prefix}',
161                          '%{_tmproot}',
162                          '%{buildroot}',
163                          '%{_builddir}',
164                          '%{_host}']
165        macros_to_copy = [('%{_host}',     '%{_build}'),
166                          ('%{_tmproot}',  '%{_tmpcxcroot}'),
167                          ('%{buildroot}', '%{buildcxcroot}'),
168                          ('%{_builddir}', '%{_buildcxcdir}')]
169        orig_macros = {}
170        for m in macros_to_save:
171            orig_macros[m] = _build.config.macro(m)
172        for m in macros_to_copy:
173            _build.config.set_define(m[0], _build.config.macro(m[1]))
[4f26bdb]174        _build.make()
[e45a2e4]175        for m in macros_to_save:
[cb12e48]176            _build.config.set_define(m, orig_macros[m])
[ebf8a1f]177        if not _build.macros.get('%{_disable_collecting}'):
178            self.root_copy(_build.config.expand('%{buildcxcroot}'),
179                           _build.config.expand('%{_tmpcxcroot}'))
[4f26bdb]180
181    def build_package(self, _config, _build):
[ebf8a1f]182        if not _build.disabled():
183            if _build.canadian_cross():
184                self.canadian_cross(_build)
185            _build.make()
186            if not _build.macros.get('%{_disable_collecting}'):
187                self.root_copy(_build.config.expand('%{buildroot}'),
188                               _build.config.expand('%{_tmproot}'))
[4f26bdb]189
190    def bset_tar(self, _build):
191        tardir = _build.config.expand('%{_tardir}')
[ebf8a1f]192        if self.opts.get_arg('--bset-tar-file') \
193           and not _build.macros.get('%{_disable_packaging}'):
[ee47d72]194            path.mkdir(tardir)
195            tar = path.join(tardir, _build.config.expand('%s.tar.bz2' % (self.bset_pkg)))
[5142bec]196            log.notice('tarball: %s' % (os.path.relpath(path.host(tar))))
[cafbcc6]197            if not self.opts.dry_run():
[4f26bdb]198                tmproot = _build.config.expand('%{_tmproot}')
[cafbcc6]199                cmd = _build.config.expand("'cd " + tmproot + \
200                                               " && %{__tar} -cf - . | %{__bzip2} > " + tar + "'")
201                _build.run(cmd, shell_opts = '-c', cwd = tmproot)
[bf13d27]202
[2f72d35]203    def parse(self, bset):
[bf13d27]204
205        def _clean(line):
206            line = line[0:-1]
207            b = line.find('#')
208            if b >= 0:
209                line = line[1:b]
210            return line.strip()
211
[e9af460]212        bsetname = bset
[bf13d27]213
[ab8319a]214        if not path.exists(bsetname):
[cb12e48]215            for cp in self.macros.expand('%{_configdir}').split(':'):
[ab8319a]216                configdir = path.abspath(cp)
[e9af460]217                bsetname = path.join(configdir, bset)
[ab8319a]218                if path.exists(bsetname):
[bf13d27]219                    break
[ab8319a]220                bsetname = None
221            if bsetname is None:
[e9af460]222                raise error.general('no build set file found: %s' % (bset))
[bf13d27]223        try:
[5142bec]224            log.trace('_bset: %s: open: %s' % (self.bset, bsetname))
[ab8319a]225            bset = open(path.host(bsetname), 'r')
[bf13d27]226        except IOError, err:
[ab8319a]227            raise error.general('error opening bset file: %s' % (bsetname))
[bf13d27]228
229        configs = []
230
231        try:
232            lc = 0
[ab8319a]233            for l in bset:
[bf13d27]234                lc += 1
235                l = _clean(l)
236                if len(l) == 0:
237                    continue
[5142bec]238                log.trace('_bset: %s: %03d: %s' % (self.bset, lc, l))
[5cba075]239                ls = l.split()
240                if ls[0][-1] == ':' and ls[0][:-1] == 'package':
[cb12e48]241                    self.bset_pkg = self.macros.expand(ls[1].strip())
242                    self.macros['package'] = self.bset_pkg
[5cba075]243                elif ls[0][0] == '%':
[a083b52]244                    def err(msg):
245                        raise error.general('%s:%d: %s' % (self.bset, lc, msg))
[5cba075]246                    if ls[0] == '%define':
[6fad89b]247                        if len(ls) > 2:
[cb12e48]248                            self.macros.define(ls[1].strip(),
249                                               ' '.join([f.strip() for f in ls[2:]]))
[6fad89b]250                        else:
[cb12e48]251                            self.macros.define(ls[1].strip())
[fa653c2]252                    elif ls[0] == '%undefine':
253                        if len(ls) > 2:
[8a1e7a0]254                            raise error.general('%s:%d: %undefine requires just the name' % \
255                                                    (self.bset, lc))
[cb12e48]256                        self.macros.undefine(ls[1].strip())
[5cba075]257                    elif ls[0] == '%include':
[2f72d35]258                        configs += self.parse(ls[1].strip())
[a083b52]259                    elif ls[0] in ['%patch', '%source']:
[9a15c40]260                        sources.process(ls[0][1:], ls[1:], self.macros, err)
[a083b52]261                    elif ls[0] == '%hash':
262                        sources.hash(ls[1:], self.macros, err)
[bf13d27]263                else:
[fba1136]264                    l = l.strip()
[0759d98]265                    c = build.find_config(l, self.configs)
[fba1136]266                    if c is None:
[8a1e7a0]267                        raise error.general('%s:%d: cannot find file: %s' % (self.bset, lc, l))
[fba1136]268                    configs += [c]
[bf13d27]269        except:
[ab8319a]270            bset.close()
[bf13d27]271            raise
272
[ab8319a]273        bset.close()
[bf13d27]274
275        return configs
276
[2f72d35]277    def load(self):
278
[adf0946]279        #
280        # If the build set file ends with .cfg the user has passed to the
281        # buildset builder a configuration so we just return it.
282        #
283        if self.bset.endswith('.cfg'):
284            configs = [self.bset]
[2f72d35]285        else:
[adf0946]286            exbset = self.macros.expand(self.bset)
287            self.macros['_bset'] = exbset
288            root, ext = path.splitext(exbset)
289            if exbset.endswith('.bset'):
290                bset = exbset
291            else:
292                bset = '%s.bset' % (exbset)
293            configs = self.parse(bset)
294        return configs
[2f72d35]295
[72f89c5]296    def build(self, deps = None, nesting_count = 0):
297
[120e101]298        build_error = False
299
[72f89c5]300        nesting_count += 1
[bf13d27]301
[5142bec]302        log.trace('_bset: %s: make' % (self.bset))
303        log.notice('Build Set: %s' % (self.bset))
[bf13d27]304
[97a685f]305        if self.opts.get_arg('--mail'):
[df56f7e]306            mail_report_subject = '%s %s' % (self.bset, self.macros.expand('%{_host}'))
[adf0946]307
[bf13d27]308        current_path = os.environ['PATH']
[0add2ea]309
[251a42d]310        start = datetime.datetime.now()
311
[df56f7e]312        mail_report = False
[2802080]313        have_errors = False
[df56f7e]314
[bf13d27]315        try:
[120e101]316            configs = self.load()
317
318            log.trace('_bset: %s: configs: %s'  % (self.bset, ','.join(configs)))
319
[bf13d27]320            builds = []
321            for s in range(0, len(configs)):
[ebf8a1f]322                b = None
[729f0bb]323                try:
[984e4e6]324                    #
325                    # Each section of the build set gets a separate set of
[cb12e48]326                    # macros so we do not contaminate one configuration with
[984e4e6]327                    # another.
328                    #
[cb12e48]329                    opts = copy.copy(self.opts)
330                    macros = copy.copy(self.macros)
[729f0bb]331                    if configs[s].endswith('.bset'):
[72f89c5]332                        log.trace('_bset: == %2d %s' % (nesting_count + 1, '=' * 75))
[cb12e48]333                        bs = buildset(configs[s], self.configs, opts, macros)
[72f89c5]334                        bs.build(deps, nesting_count)
[729f0bb]335                        del bs
336                    elif configs[s].endswith('.cfg'):
[df56f7e]337                        mail_report = self.opts.get_arg('--mail')
[72f89c5]338                        log.trace('_bset: -- %2d %s' % (nesting_count + 1, '-' * 75))
[120e101]339                        try:
340                            b = build.build(configs[s], self.opts.get_arg('--pkg-tar-files'),
341                                            opts, macros)
342                        except:
343                            build_error = True
344                            raise
[01b2815]345                        if b.macros.get('%{_disable_reporting}'):
346                            mail_report = False
[79f80fd]347                        if deps is None:
[4f26bdb]348                            self.build_package(configs[s], b)
[ce60578]349                            self.report(configs[s], b,
350                                        copy.copy(self.opts),
351                                        copy.copy(self.macros))
[4bd058e]352                            # Always product an XML report.
353                            self.report(configs[s], b,
354                                        copy.copy(self.opts),
355                                        copy.copy(self.macros),
356                                        format = 'xml')
[2802080]357                            if s == len(configs) - 1 and not have_errors:
[4f26bdb]358                                self.bset_tar(b)
[79f80fd]359                        else:
360                            deps += b.config.includes()
[729f0bb]361                        builds += [b]
362                    else:
363                        raise error.general('invalid config type: %s' % (configs[s]))
364                except error.general, gerr:
[2802080]365                    have_errors = True
[ebf8a1f]366                    if b is not None:
367                        if self.build_failure is None:
368                            self.build_failure = b.name()
369                        self.write_mail_header('')
370                        self.write_mail_header('= ' * 40)
371                        self.write_mail_header('Build FAILED: %s' % (b.name()))
372                        self.write_mail_header('- ' * 40)
373                        self.write_mail_header(str(log.default))
374                        self.write_mail_header('- ' * 40)
375                        if self.opts.keep_going():
[01b2815]376                            log.notice(str(gerr))
[ebf8a1f]377                            if self.opts.always_clean():
378                                builds += [b]
379                        else:
380                            raise
[729f0bb]381                    else:
382                        raise
[b843e62]383            if deps is None \
384               and not self.opts.no_install() \
385               and not have_errors:
[4f26bdb]386                for b in builds:
[b843e62]387                    if not b.canadian_cross() \
388                       and not b.disabled() \
[ebf8a1f]389                       and not b.macros.get('%{_disable_installing}'):
390                        self.install(b.name(),
391                                     b.config.expand('%{buildroot}'),
392                                     b.config.expand('%{_prefix}'))
[4f26bdb]393            if deps is None and \
[0add2ea]394                    (not self.opts.no_clean() or self.opts.always_clean()):
[bf13d27]395                for b in builds:
[ebf8a1f]396                    if not b.disabled():
397                        log.notice('cleaning: %s' % (b.name()))
398                        b.cleanup()
[bf13d27]399            for b in builds:
400                del b
[df56f7e]401        except error.general, gerr:
[120e101]402            if not build_error:
403                log.stderr(str(gerr))
[df56f7e]404            raise
405        except KeyboardInterrupt:
406            mail_report = False
407            raise
[bf13d27]408        except:
[df56f7e]409            self.build_failure = 'RSB general failure'
[bf13d27]410            raise
[c914e1d]411        finally:
412            end = datetime.datetime.now()
413            os.environ['PATH'] = current_path
414            build_time = str(end - start)
[df56f7e]415            if mail_report:
[c914e1d]416                to_addr = self.opts.get_arg('--mail-to')
417                if to_addr is not None:
418                    to_addr = to_addr[1]
419                else:
420                    to_addr = self.macros.expand('%{_mail_tools_to}')
421                log.notice('Mailing report: %s' % (to_addr))
422                self.write_mail_header('Build Time %s' % (build_time), True)
423                self.write_mail_header('')
424                m = mailer.mail(self.opts)
[df56f7e]425                if self.build_failure is not None:
426                    mail_report_subject = 'Build: FAILED %s (%s)' %\
427                        (mail_report_subject, self.build_failure)
428                    pass_fail = 'FAILED'
[c914e1d]429                else:
[df56f7e]430                    mail_report_subject = 'Build: PASSED %s' % (mail_report_subject)
[c914e1d]431                if not self.opts.dry_run():
[df56f7e]432                    m.send(to_addr, mail_report_subject,
433                           self.mail_header + self.mail_report)
[c914e1d]434            log.notice('Build Set: Time %s' % (build_time))
[adf0946]435
[d63f135]436def list_bset_cfg_files(opts, configs):
437    if opts.get_arg('--list-configs') or opts.get_arg('--list-bsets'):
438        if opts.get_arg('--list-configs'):
439            ext = '.cfg'
440        else:
441            ext = '.bset'
442        for p in configs['paths']:
443            print 'Examining: %s' % (os.path.relpath(p))
444        for c in configs['files']:
445            if c.endswith(ext):
446                print '    %s' % (c)
447        return True
448    return False
449
[bf13d27]450def run():
451    import sys
[74da24c]452    ec = 0
[120e101]453    setbuilder_error = False
[bf13d27]454    try:
[cafbcc6]455        optargs = { '--list-configs':  'List available configurations',
456                    '--list-bsets':    'List available build sets',
[79f80fd]457                    '--list-deps':     'List the dependent files.',
[adf0946]458                    '--bset-tar-file': 'Create a build set tar file',
459                    '--pkg-tar-files': 'Create package tar files',
[26595c7]460                    '--no-report':     'Do not create a package report.',
[97a685f]461                    '--report-format': 'The report format (text, html, asciidoc).' }
462        mailer.append_options(optargs)
[cb12e48]463        opts = options.load(sys.argv, optargs)
[5142bec]464        log.notice('RTEMS Source Builder - Set Builder, v%s' % (version.str()))
[72f89c5]465        opts.log_info()
[cb12e48]466        if not check.host_setup(opts):
[fba1136]467            raise error.general('host build environment is not set up correctly')
[cb12e48]468        configs = build.get_configs(opts)
[79f80fd]469        if opts.get_arg('--list-deps'):
470            deps = []
471        else:
472            deps = None
[d63f135]473        if not list_bset_cfg_files(opts, configs):
[2cc7a97]474            prefix = opts.defaults.expand('%{_prefix}')
[3963ac4]475            if not opts.dry_run() and \
[eeded98]476               not opts.canadian_cross() and \
[3963ac4]477               not opts.no_install() and \
478               not path.ispathwritable(prefix):
[2cc7a97]479                raise error.general('prefix is not writable: %s' % (path.host(prefix)))
[71b8893]480            for bset in opts.params():
[120e101]481                setbuilder_error = True
[cb12e48]482                b = buildset(bset, configs, opts)
[79f80fd]483                b.build(deps)
[74da24c]484                b = None
[120e101]485                setbuilder_error = False
[79f80fd]486        if deps is not None:
487            c = 0
488            for d in sorted(set(deps)):
489                c += 1
490                print 'dep[%d]: %s' % (c, d)
[bf13d27]491    except error.general, gerr:
[120e101]492        if not setbuilder_error:
493            log.stderr(str(gerr))
[74da24c]494        log.stderr('Build FAILED')
495        ec = 1
[bf13d27]496    except error.internal, ierr:
[120e101]497        if not setbuilder_error:
498            log.stderr(str(ierr))
[74da24c]499        log.stderr('Internal Build FAILED')
500        ec = 1
[bf13d27]501    except error.exit, eerr:
502        pass
503    except KeyboardInterrupt:
[5142bec]504        log.notice('abort: user terminated')
[74da24c]505        ec = 1
506    sys.exit(ec)
[bf13d27]507
508if __name__ == "__main__":
509    run()
Note: See TracBrowser for help on using the repository browser.