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

4.104.114.95
Last change on this file since adf0946 was adf0946, checked in by Chris Johns <chrisj@…>, on 04/28/13 at 23:01:14

Report from the setbuilder's build config.

Refactor the reporter to allow the setbuilder to use its build config
rather than regenerating the configuration from the configuration file.
Using the config file and the build macros exposed an issue if a
macro was undefined that was defined in a build set above the
config file. Using the build set's configuration as used to build
is a better solution.

The reporter was refactored to allow a config class to be used
to report.

The setbuild can now take a configuration file as an input file.

  • Property mode set to 100644
File size: 16.2 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 distutils.dir_util
28import glob
29import operator
30import os
31import sys
32
33try:
34    import build
35    import check
36    import error
37    import log
38    import options
39    import path
40    import reports
41    import version
42except KeyboardInterrupt:
43    print 'abort: user terminated'
44    sys.exit(1)
45except:
46    print 'error: unknown application load error'
47    sys.exit(1)
48
49class buildset:
50    """Build a set builds a set of packages."""
51
52    def __init__(self, bset, _configs, opts, macros = None):
53        log.trace('_bset: %s: init' % (bset))
54        self.configs = _configs
55        self.opts = opts
56        if macros is None:
57            self.macros = copy.copy(opts.defaults)
58        else:
59            self.macros = copy.copy(macros)
60        self.bset = bset
61        self.bset_pkg = '%s-%s-set' % (self.macros.expand('%{_target}'), self.bset)
62        self.email_report = ''
63
64    def write_email_report(self, text, prepend = False):
65        if len(text) == 0 or text[-1] != '\n' or text[-1] != '\r':
66            text += os.linesep
67        if prepend:
68            self.email_report = text + self.email_report
69        else:
70            self.email_report += text
71
72    def copy(self, src, dst):
73        if not os.path.isdir(path.host(src)):
74            raise error.general('copying tree: no source directory: %s' % (path.host(src)))
75        if not self.opts.dry_run():
76            try:
77                files = distutils.dir_util.copy_tree(path.host(src),
78                                                     path.host(dst))
79                for f in files:
80                    log.output(f)
81            except IOError, err:
82                raise error.general('copying tree: %s -> %s: %s' % (src, dst, str(err)))
83            except distutils.errors.DistutilsFileError, err:
84                raise error.general('copying tree: %s' % (str(err)))
85
86    def report(self, _config, _build):
87        if not _build.opts.get_arg('--no-report') and not _build.opts.get_arg('--no-email'):
88            format = _build.opts.get_arg('--report-format')
89            if format is None:
90                format = 'html'
91                ext = '.html'
92            else:
93                if len(format) != 2:
94                    raise error.general('invalid report format option: %s' % ('='.join(format)))
95                if format[1] == 'text':
96                    format = 'text'
97                    ext = '.txt'
98                elif format[1] == 'asciidoc':
99                    format = 'asciidoc'
100                    ext = '.txt'
101                elif format[1] == 'html':
102                    format = 'html'
103                    ext = '.html'
104                else:
105                    raise error.general('invalid report format: %s' % (format[1]))
106            buildroot = _build.config.abspath('%{buildroot}')
107            prefix = _build.macros.expand('%{_prefix}')
108            name = _build.main_package().name() + ext
109            log.notice('reporting: %s -> %s' % (_config, name))
110            if not _build.opts.get_arg('--no-report'):
111                outpath = path.host(path.join(buildroot, prefix, 'share', 'rtems-source-builder'))
112                outname = path.host(path.join(outpath, name))
113                r = reports.report(format, self.configs, _build.opts, _build.macros)
114                r.setup()
115                r.introduction(_build.config.file_name())
116                r.config(_build.config, _build.opts, _build.macros)
117                if not _build.opts.dry_run():
118                    _build.mkdir(outpath)
119                    r.write(outname)
120                del r
121            if not _build.opts.get_arg('--no-email'):
122                r = reports.report('text', self.configs, _build.opts, _build.macros)
123                r.setup()
124                r.introduction(_build.config.file_name())
125                r.config(_build.config, _build.opts, _build.macros)
126                self.email_report += r.out
127                del r
128
129    def root_copy(self, src, dst):
130        what = '%s -> %s' % \
131            (os.path.relpath(path.host(src)), os.path.relpath(path.host(dst)))
132        log.trace('_bset: %s: collecting: %s' % (self.bset, what))
133        if not self.opts.dry_run():
134            self.copy(src, dst)
135
136    def install(self, name, buildroot, prefix):
137        dst = prefix
138        src = path.join(buildroot, prefix)
139        log.notice('installing: %s -> %s' % (name, path.host(dst)))
140        if not self.opts.dry_run():
141            self.copy(src, dst)
142
143    def canadian_cross(self, _build):
144        # @fixme Switch to using a private macros map.
145        macros_to_save = ['%{_prefix}',
146                          '%{_tmproot}',
147                          '%{buildroot}',
148                          '%{_builddir}',
149                          '%{_host}']
150        macros_to_copy = [('%{_host}',     '%{_build}'),
151                          ('%{_tmproot}',  '%{_tmpcxcroot}'),
152                          ('%{buildroot}', '%{buildcxcroot}'),
153                          ('%{_builddir}', '%{_buildcxcdir}')]
154        orig_macros = {}
155        for m in macros_to_save:
156            orig_macros[m] = _build.config.macro(m)
157        for m in macros_to_copy:
158            _build.config.set_define(m[0], _build.config.macro(m[1]))
159        _build.make()
160        for m in macros_to_save:
161            _build.config.set_define(m, orig_macros[m])
162        self.root_copy(_build.config.expand('%{buildcxcroot}'),
163                       _build.config.expand('%{_tmpcxcroot}'))
164
165    def build_package(self, _config, _build):
166        if _build.canadian_cross():
167            self.canadian_cross(_build)
168        _build.make()
169        self.report(_config, _build)
170        self.root_copy(_build.config.expand('%{buildroot}'),
171                       _build.config.expand('%{_tmproot}'))
172
173    def bset_tar(self, _build):
174        tardir = _build.config.expand('%{_tardir}')
175        if self.opts.get_arg('--bset-tar-file'):
176            path.mkdir(tardir)
177            tar = path.join(tardir, _build.config.expand('%s.tar.bz2' % (self.bset_pkg)))
178            log.notice('tarball: %s' % (os.path.relpath(path.host(tar))))
179            if not self.opts.dry_run():
180                tmproot = _build.config.expand('%{_tmproot}')
181                cmd = _build.config.expand("'cd " + tmproot + \
182                                               " && %{__tar} -cf - . | %{__bzip2} > " + tar + "'")
183                _build.run(cmd, shell_opts = '-c', cwd = tmproot)
184
185    def parse(self, bset):
186
187        def _clean(line):
188            line = line[0:-1]
189            b = line.find('#')
190            if b >= 0:
191                line = line[1:b]
192            return line.strip()
193
194        bsetname = bset
195
196        if not path.exists(bsetname):
197            for cp in self.macros.expand('%{_configdir}').split(':'):
198                configdir = path.abspath(cp)
199                bsetname = path.join(configdir, bset)
200                if path.exists(bsetname):
201                    break
202                bsetname = None
203            if bsetname is None:
204                raise error.general('no build set file found: %s' % (bset))
205        try:
206            log.trace('_bset: %s: open: %s' % (self.bset, bsetname))
207            bset = open(path.host(bsetname), 'r')
208        except IOError, err:
209            raise error.general('error opening bset file: %s' % (bsetname))
210
211        configs = []
212
213        try:
214            lc = 0
215            for l in bset:
216                lc += 1
217                l = _clean(l)
218                if len(l) == 0:
219                    continue
220                log.trace('_bset: %s: %03d: %s' % (self.bset, lc, l))
221                ls = l.split()
222                if ls[0][-1] == ':' and ls[0][:-1] == 'package':
223                    self.bset_pkg = self.macros.expand(ls[1].strip())
224                    self.macros['package'] = self.bset_pkg
225                elif ls[0][0] == '%':
226                    if ls[0] == '%define':
227                        if len(ls) > 2:
228                            self.macros.define(ls[1].strip(),
229                                               ' '.join([f.strip() for f in ls[2:]]))
230                        else:
231                            self.macros.define(ls[1].strip())
232                    elif ls[0] == '%undefine':
233                        if len(ls) > 2:
234                            raise error.general('%undefine requires just the name')
235                        self.macros.undefine(ls[1].strip())
236                    elif ls[0] == '%include':
237                        configs += self.parse(ls[1].strip())
238                    else:
239                        raise error.general('invalid directive in build set files: %s' % (l))
240                else:
241                    l = l.strip()
242                    c = build.find_config(l, self.configs)
243                    if c is None:
244                        raise error.general('cannot find file: %s' % (l))
245                    configs += [c]
246        except:
247            bset.close()
248            raise
249
250        bset.close()
251
252        return configs
253
254    def load(self):
255
256        #
257        # If the build set file ends with .cfg the user has passed to the
258        # buildset builder a configuration so we just return it.
259        #
260        if self.bset.endswith('.cfg'):
261            configs = [self.bset]
262        else:
263            exbset = self.macros.expand(self.bset)
264            self.macros['_bset'] = exbset
265            root, ext = path.splitext(exbset)
266            if exbset.endswith('.bset'):
267                bset = exbset
268            else:
269                bset = '%s.bset' % (exbset)
270            configs = self.parse(bset)
271        return configs
272
273    def build(self, deps = None):
274
275        log.trace('_bset: %s: make' % (self.bset))
276        log.notice('Build Set: %s' % (self.bset))
277
278        if not self.opts.get_arg('--no-email'):
279            email_report_header = \
280                'Build Set: %s (%s)' % (self.bset, datetime.datetime.now().ctime())
281
282        configs = self.load()
283
284        log.trace('_bset: %s: configs: %s'  % (self.bset, ','.join(configs)))
285
286        current_path = os.environ['PATH']
287
288        start = datetime.datetime.now()
289
290        try:
291            builds = []
292            for s in range(0, len(configs)):
293                try:
294                    #
295                    # Each section of the build set gets a separate set of
296                    # macros so we do not contaminate one configuration with
297                    # another.
298                    #
299                    opts = copy.copy(self.opts)
300                    macros = copy.copy(self.macros)
301                    if configs[s].endswith('.bset'):
302                        log.trace('_bset: %s' % ('=' * 80))
303                        bs = buildset(configs[s], self.configs, opts, macros)
304                        bs.build(deps)
305                        del bs
306                    elif configs[s].endswith('.cfg'):
307                        log.trace('_bset: %s' % ('-' * 80))
308                        b = build.build(configs[s], self.opts.get_arg('--pkg-tar-files'),
309                                        opts, macros)
310                        if deps is None:
311                            self.build_package(configs[s], b)
312                            if s == len(configs) - 1:
313                                self.bset_tar(b)
314                        else:
315                            deps += b.config.includes()
316                        builds += [b]
317                    else:
318                        raise error.general('invalid config type: %s' % (configs[s]))
319                except error.general, gerr:
320                    self.write_email_report(str(gerr))
321                    if self.opts.keep_going():
322                        print gerr
323                        if self.opts.always_clean():
324                            builds += [b]
325                    else:
326                        raise
327            if deps is None and not self.opts.no_install():
328                for b in builds:
329                    self.install(b.name(),
330                                 b.config.expand('%{buildroot}'),
331                                 b.config.expand('%{_prefix}'))
332            if deps is None and \
333                    (not self.opts.no_clean() or self.opts.always_clean()):
334                for b in builds:
335                    log.notice('cleaning: %s' % (b.name()))
336                    b.cleanup()
337            for b in builds:
338                del b
339        except:
340            os.environ['PATH'] = current_path
341            raise
342
343        end = datetime.datetime.now()
344
345        os.environ['PATH'] = current_path
346
347        log.notice('Build Set: Time %s' % (str(end - start)))
348
349        if not self.opts.get_arg('--no-email'):
350            self.write_email_report('', True)
351            self.write_email_report('  Build Time %s' % (str(end - start)), True)
352            self.write_email_report(email_report_header, True)
353
354def list_bset_cfg_files(opts, configs):
355    if opts.get_arg('--list-configs') or opts.get_arg('--list-bsets'):
356        if opts.get_arg('--list-configs'):
357            ext = '.cfg'
358        else:
359            ext = '.bset'
360        for p in configs['paths']:
361            print 'Examining: %s' % (os.path.relpath(p))
362        for c in configs['files']:
363            if c.endswith(ext):
364                print '    %s' % (c)
365        return True
366    return False
367
368def run():
369    import sys
370    try:
371        optargs = { '--list-configs':  'List available configurations',
372                    '--list-bsets':    'List available build sets',
373                    '--list-deps':     'List the dependent files.',
374                    '--bset-tar-file': 'Create a build set tar file',
375                    '--pkg-tar-files': 'Create package tar files',
376                    '--no-report':     'Do not create a package report.',
377                    '--report-format': 'The report format (text, html, asciidoc).',
378                    '--no-email':      'Do not send an email report.',
379                    '--smtp-host':     'SMTP host to send via.',
380                    '--email-to':      'Email address to send the email too.',
381                    '--email-from':    'Email address the report is from.' }
382        opts = options.load(sys.argv, optargs)
383        log.notice('RTEMS Source Builder - Set Builder, v%s' % (version.str()))
384        if not check.host_setup(opts):
385            raise error.general('host build environment is not set up correctly')
386        configs = build.get_configs(opts)
387        if opts.get_arg('--list-deps'):
388            deps = []
389        else:
390            deps = None
391        if not list_bset_cfg_files(opts, configs):
392            prefix = opts.defaults.expand('%{_prefix}')
393            if not opts.dry_run() and not opts.no_install() and \
394                    not path.ispathwritable(prefix):
395                raise error.general('prefix is not writable: %s' % (path.host(prefix)))
396            for bset in opts.params():
397                b = buildset(bset, configs, opts)
398                b.build(deps)
399                del b
400        if deps is not None:
401            c = 0
402            for d in sorted(set(deps)):
403                c += 1
404                print 'dep[%d]: %s' % (c, d)
405    except error.general, gerr:
406        print gerr
407        print >> sys.stderr, 'Build FAILED'
408        sys.exit(1)
409    except error.internal, ierr:
410        print ierr
411        sys.exit(1)
412    except error.exit, eerr:
413        pass
414    except KeyboardInterrupt:
415        log.notice('abort: user terminated')
416        sys.exit(1)
417    sys.exit(0)
418
419if __name__ == "__main__":
420    run()
Note: See TracBrowser for help on using the repository browser.