source: rtems-source-builder/source-builder/sb/setbuilder.py @ 29819a2

4.104.95
Last change on this file since 29819a2 was 29819a2, checked in by Chris Johns <chrisj@…>, on 03/19/16 at 06:23:50

sb: fix the if logic to handle directives in if statements.

Directives in if statements end at the end of an if. The cannot
change or split across directive boundaries.

Add more trace detail to config.py.

Updates #2661.

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