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 Mar 7, 2016 at 12:56:02 AM

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
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    raise
50    print('error: unknown application load error', file = sys.stderr)
51    sys.exit(1)
52
53class buildset:
54    """Build a set builds a set of packages."""
55
56    def __init__(self, bset, _configs, opts, macros = None):
57        log.trace('_bset: %s: init' % (bset))
58        self.configs = _configs
59        self.opts = opts
60        if macros is None:
61            self.macros = copy.copy(opts.defaults)
62        else:
63            self.macros = copy.copy(macros)
64        self.bset = bset
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)
71        self.mail_header = ''
72        self.mail_report = ''
73        self.build_failure = None
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
82
83    def write_mail_report(self, text, prepend = False):
84        if len(text) == 0 or text[-1] != '\n' or text[-1] != '\r':
85            text += os.linesep
86        if prepend:
87            self.mail_report = text + self.mail_report
88        else:
89            self.mail_report += text
90
91    def copy(self, src, dst):
92        log.output('copy: %s => %s' % (path.host(src), path.host(dst)))
93        if not self.opts.dry_run():
94            path.copy_tree(src, dst)
95
96    def report(self, _config, _build, opts, macros, format = None):
97        if len(_build.main_package().name()) > 0 \
98           and not _build.macros.get('%{_disable_reporting}') \
99           and (not _build.opts.get_arg('--no-report') \
100                or _build.opts.get_arg('--mail')):
101            if format is None:
102                format = _build.opts.get_arg('--report-format')
103                if format is not None:
104                    if len(format) != 2:
105                        raise error.general('invalid report format option: %s' % \
106                                            ('='.join(format)))
107                    format = format[1]
108            if format is None:
109                format = 'text'
110            if format == 'text':
111                ext = '.txt'
112            elif format == 'asciidoc':
113                ext = '.txt'
114            elif format == 'html':
115                ext = '.html'
116            elif format == 'xml':
117                ext = '.xml'
118            elif format == 'ini':
119                ext = '.ini'
120            else:
121                raise error.general('invalid report format: %s' % (format))
122            buildroot = _build.config.abspath('%{buildroot}')
123            prefix = _build.macros.expand('%{_prefix}')
124            name = _build.main_package().name() + ext
125            log.notice('reporting: %s -> %s' % (_config, name))
126            if not _build.opts.get_arg('--no-report'):
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))
134                r.introduction(_build.config.file_name())
135                r.generate(_build.config.file_name())
136                r.epilogue(_build.config.file_name())
137                if not _build.opts.dry_run():
138                    _build.mkdir(outpath)
139                    r.write(outname)
140                del r
141            if _build.opts.get_arg('--mail'):
142                r = reports.report('text', self.configs,
143                                   copy.copy(opts), copy.copy(macros))
144                r.introduction(_build.config.file_name())
145                r.generate(_build.config.file_name())
146                r.epilogue(_build.config.file_name())
147                self.write_mail_report(r.out)
148                del r
149
150    def root_copy(self, src, dst):
151        what = '%s -> %s' % \
152            (os.path.relpath(path.host(src)), os.path.relpath(path.host(dst)))
153        log.trace('_bset: %s: collecting: %s' % (self.bset, what))
154        self.copy(src, dst)
155
156    def install(self, name, buildroot, prefix):
157        dst = prefix
158        src = path.join(buildroot, prefix)
159        log.notice('installing: %s -> %s' % (name, path.host(dst)))
160        self.copy(src, dst)
161
162    def canadian_cross(self, _build):
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()
174        for m in macros_to_copy:
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()
179        _build.make()
180        if not _build.macros.get('%{_disable_collecting}'):
181            self.root_copy(_build.config.expand('%{buildroot}'),
182                           _build.config.expand('%{_tmproot}'))
183        _build.set_macros(_build.copy_init_macros())
184        _build.reload()
185
186    def build_package(self, _config, _build):
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}'))
194
195    def bset_tar(self, _build):
196        tardir = _build.config.expand('%{_tardir}')
197        if (self.opts.get_arg('--bset-tar-file') or self.opts.canadian_cross()) \
198           and not _build.macros.get('%{_disable_packaging}'):
199            path.mkdir(tardir)
200            tar = path.join(tardir, _build.config.expand('%s.tar.bz2' % (self.bset_pkg)))
201            log.notice('tarball: %s' % (os.path.relpath(path.host(tar))))
202            if not self.opts.dry_run():
203                tmproot = _build.config.expand('%{_tmproot}')
204                cmd = _build.config.expand('"cd ' + tmproot + \
205                                               ' && %{__tar} -cf - . | %{__bzip2} > ' + tar + '"')
206                _build.run(cmd, shell_opts = '-c', cwd = tmproot)
207
208    def parse(self, bset):
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
217        bsetname = bset
218
219        if not path.exists(bsetname):
220            for cp in self.macros.expand('%{_configdir}').split(':'):
221                configdir = path.abspath(cp)
222                bsetname = path.join(configdir, bset)
223                if path.exists(bsetname):
224                    break
225                bsetname = None
226            if bsetname is None:
227                raise error.general('no build set file found: %s' % (bset))
228        try:
229            log.trace('_bset: %s: open: %s' % (self.bset, bsetname))
230            bset = open(path.host(bsetname), 'r')
231        except IOError as err:
232            raise error.general('error opening bset file: %s' % (bsetname))
233
234        configs = []
235
236        try:
237            lc = 0
238            for l in bset:
239                lc += 1
240                l = _clean(l)
241                if len(l) == 0:
242                    continue
243                log.trace('_bset: %s: %03d: %s' % (self.bset, lc, l))
244                ls = l.split()
245                if ls[0][-1] == ':' and ls[0][:-1] == 'package':
246                    self.bset_pkg = self.macros.expand(ls[1].strip())
247                    self.macros['package'] = self.bset_pkg
248                elif ls[0][0] == '%':
249                    def err(msg):
250                        raise error.general('%s:%d: %s' % (self.bset, lc, msg))
251                    if ls[0] == '%define':
252                        if len(ls) > 2:
253                            self.macros.define(ls[1].strip(),
254                                               ' '.join([f.strip() for f in ls[2:]]))
255                        else:
256                            self.macros.define(ls[1].strip())
257                    elif ls[0] == '%undefine':
258                        if len(ls) > 2:
259                            raise error.general('%s:%d: %undefine requires just the name' % \
260                                                    (self.bset, lc))
261                        self.macros.undefine(ls[1].strip())
262                    elif ls[0] == '%include':
263                        configs += self.parse(ls[1].strip())
264                    elif ls[0] in ['%patch', '%source']:
265                        sources.process(ls[0][1:], ls[1:], self.macros, err)
266                    elif ls[0] == '%hash':
267                        sources.hash(ls[1:], self.macros, err)
268                else:
269                    l = l.strip()
270                    c = build.find_config(l, self.configs)
271                    if c is None:
272                        raise error.general('%s:%d: cannot find file: %s' % (self.bset, lc, l))
273                    configs += [c]
274        except:
275            bset.close()
276            raise
277
278        bset.close()
279
280        return configs
281
282    def load(self):
283
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]
290        else:
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
300
301    def build(self, deps = None, nesting_count = 0):
302
303        build_error = False
304
305        nesting_count += 1
306
307        log.trace('_bset: %s: make' % (self.bset))
308        log.notice('Build Set: %s' % (self.bset))
309
310        if self.opts.get_arg('--mail'):
311            mail_report_subject = '%s %s' % (self.bset, self.macros.expand('%{_host}'))
312
313        current_path = os.environ['PATH']
314
315        start = datetime.datetime.now()
316
317        mail_report = False
318        have_errors = False
319
320        try:
321            configs = self.load()
322
323            log.trace('_bset: %s: configs: %s'  % (self.bset, ','.join(configs)))
324
325            builds = []
326            for s in range(0, len(configs)):
327                b = None
328                try:
329                    #
330                    # Each section of the build set gets a separate set of
331                    # macros so we do not contaminate one configuration with
332                    # another.
333                    #
334                    opts = copy.copy(self.opts)
335                    macros = copy.copy(self.macros)
336                    if configs[s].endswith('.bset'):
337                        log.trace('_bset: == %2d %s' % (nesting_count + 1, '=' * 75))
338                        bs = buildset(configs[s], self.configs, opts, macros)
339                        bs.build(deps, nesting_count)
340                        del bs
341                    elif configs[s].endswith('.cfg'):
342                        mail_report = self.opts.get_arg('--mail')
343                        log.trace('_bset: -- %2d %s' % (nesting_count + 1, '-' * 75))
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
350                        if b.macros.get('%{_disable_reporting}'):
351                            mail_report = False
352                        if deps is None:
353                            self.build_package(configs[s], b)
354                            self.report(configs[s], b,
355                                        copy.copy(self.opts),
356                                        copy.copy(self.macros))
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')
362                            if s == len(configs) - 1 and not have_errors:
363                                self.bset_tar(b)
364                        else:
365                            deps += b.config.includes()
366                        builds += [b]
367                    else:
368                        raise error.general('invalid config type: %s' % (configs[s]))
369                except error.general as gerr:
370                    have_errors = True
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():
381                            log.notice(str(gerr))
382                            if self.opts.always_clean():
383                                builds += [b]
384                        else:
385                            raise
386                    else:
387                        raise
388            #
389            # Installing ...
390            #
391            log.trace('_bset: installing: deps:%r no-install:%r' % \
392                      (deps is None, self.opts.no_install()))
393            if deps is None \
394               and not self.opts.no_install() \
395               and not have_errors:
396                for b in builds:
397                    log.trace('_bset: installing: %r' % b.installable())
398                    if b.installable():
399                        self.install(b.name(),
400                                     b.config.expand('%{buildroot}'),
401                                     b.config.expand('%{_prefix}'))
402
403            if deps is None and \
404                    (not self.opts.no_clean() or self.opts.always_clean()):
405                for b in builds:
406                    if not b.disabled():
407                        log.notice('cleaning: %s' % (b.name()))
408                        b.cleanup()
409            for b in builds:
410                del b
411        except error.general as gerr:
412            if not build_error:
413                log.stderr(str(gerr))
414            raise
415        except KeyboardInterrupt:
416            mail_report = False
417            raise
418        except:
419            self.build_failure = 'RSB general failure'
420            raise
421        finally:
422            end = datetime.datetime.now()
423            os.environ['PATH'] = current_path
424            build_time = str(end - start)
425            if mail_report:
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)
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'
439                else:
440                    mail_report_subject = 'Build: PASSED %s' % (mail_report_subject)
441                if not self.opts.dry_run():
442                    m.send(to_addr, mail_report_subject,
443                           self.mail_header + self.mail_report)
444            log.notice('Build Set: Time %s' % (build_time))
445
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']:
453            print('Examining: %s' % (os.path.relpath(p)))
454        for c in configs['files']:
455            if c.endswith(ext):
456                print('    %s' % (c))
457        return True
458    return False
459
460def run():
461    import sys
462    ec = 0
463    setbuilder_error = False
464    try:
465        optargs = { '--list-configs':  'List available configurations',
466                    '--list-bsets':    'List available build sets',
467                    '--list-deps':     'List the dependent files.',
468                    '--bset-tar-file': 'Create a build set tar file',
469                    '--pkg-tar-files': 'Create package tar files',
470                    '--no-report':     'Do not create a package report.',
471                    '--report-format': 'The report format (text, html, asciidoc).' }
472        mailer.append_options(optargs)
473        opts = options.load(sys.argv, optargs)
474        log.notice('RTEMS Source Builder - Set Builder, %s' % (version.str()))
475        opts.log_info()
476        if not check.host_setup(opts):
477            raise error.general('host build environment is not set up correctly')
478        configs = build.get_configs(opts)
479        if opts.get_arg('--list-deps'):
480            deps = []
481        else:
482            deps = None
483        if not list_bset_cfg_files(opts, configs):
484            prefix = opts.defaults.expand('%{_prefix}')
485            if opts.canadian_cross():
486                opts.disable_install()
487
488            if not opts.dry_run() and \
489               not opts.canadian_cross() and \
490               not opts.no_install() and \
491               not path.ispathwritable(prefix):
492                raise error.general('prefix is not writable: %s' % (path.host(prefix)))
493            for bset in opts.params():
494                setbuilder_error = True
495                b = buildset(bset, configs, opts)
496                b.build(deps)
497                b = None
498                setbuilder_error = False
499        if deps is not None:
500            c = 0
501            for d in sorted(set(deps)):
502                c += 1
503                print('dep[%d]: %s' % (c, d))
504    except error.general as gerr:
505        if not setbuilder_error:
506            log.stderr(str(gerr))
507        log.stderr('Build FAILED')
508        ec = 1
509    except error.internal as ierr:
510        if not setbuilder_error:
511            log.stderr(str(ierr))
512        log.stderr('Internal Build FAILED')
513        ec = 1
514    except error.exit as eerr:
515        pass
516    except KeyboardInterrupt:
517        log.notice('abort: user terminated')
518        ec = 1
519    sys.exit(ec)
520
521if __name__ == "__main__":
522    run()
Note: See TracBrowser for help on using the repository browser.