source: rtems-source-builder/source-builder/sb/setbuilder.py @ f88fcf3

4.11
Last change on this file since f88fcf3 was f88fcf3, checked in by Chris Johns <chrisj@…>, on 03/07/16 at 00:56:02

sb: Update code base to support Python3 and Python2.

Fix Windows support to allow MSYS2 Python to be used.

Updates #2619.

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