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

4.11
Last change on this file since bce0563 was bce0563, checked in by Chris Johns <chrisj@…>, on 03/10/16 at 04:06:32

sb: Fix typo on the urlparse name in download.py

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