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

4.11
Last change on this file since a7691f0 was a7691f0, checked in by Chris Johns <chrisj@…>, on Feb 25, 2016 at 3:29:00 AM

Wrap the tar command in double quotes for Windows cmd.exe.

Windows cmd.exe does not support a single quote for options. Change to
a double quote.

Closes #2615.

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