source: rtems-source-builder/source-builder/sb/reports.py @ cb12e48

4.104.114.95
Last change on this file since cb12e48 was cb12e48, checked in by Chris Johns <chrisj@…>, on 04/09/13 at 03:51:43

Refactor defaults, macros and options.

To support building snapshots and pre-release source the defaults
has been refactored. The defaults have been moved to a stand alone
file and a macros.py module added. This modile abstracts the
old default dictionary turning it into a class. The macros
class can load macros from a file therefore the defaults have
been moved to a stand alone file.

The use of defaults has been removed from the project. The only
case where it is used in the options where the defaults are read
from a file. Macros are used everywhere now.

The defaults.py has been moved to the option.py and the separate
options and defaults values has been moved to a new pattern. When
constructing an object that needs macros and options if the macros
passed in is None the defaults from the options are used. This makes
it clear when the defaults are being used or when a modified set of
macros is being used.

The macros class support maps. The default is 'global' and where all
the defaults reside and where configuratiion file changes end up.
Maps allow macros to be read from a file and override the values
being maintained in the 'global' map. Reading a macro first checks
the map and if not present checks the 'global' map.

The addition of maps to the macros provides the base to support
snapshots and pre-release testing with standard configurations.
This functionality needs to be added. It works by letting to
specify a snapshot with:

source0: none, override, 'my-dist.tar.bz2'

and it will be used rather the value from the standard configuration.
With a build set you need to also specify the package these macros
are for. The maps provide this.

  • Property mode set to 100644
File size: 14.3 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 given a config file. It only builds to be
22# installed not to be package unless you run a packager around this.
23#
24
25import copy
26import datetime
27import os
28import sys
29
30try:
31    import build
32    import check
33    import config
34    import error
35    import git
36    import log
37    import options
38    import path
39    import setbuilder
40    import version
41except KeyboardInterrupt:
42    print 'user terminated'
43    sys.exit(1)
44except:
45    print 'error: unknown application load error'
46    sys.exit(1)
47
48def _notice(opts, text):
49    if not opts.quiet() and not log.default.has_stdout():
50        print text
51    log.output(text)
52    log.flush()
53
54class report:
55    """Report the build details about a package given a config file."""
56
57    line_len = 78
58
59    def __init__(self, format, _configs, opts, macros = None):
60        self.format = format
61        self.configs = _configs
62        self.opts = opts
63        if macros is None:
64            self.macros = opts.defaults
65        else:
66            self.macros = macros
67        self.bset_nesting = 0
68        self.configs_active = False
69        self.out = ''
70        self.asciidoc = None
71
72    def _sbpath(self, *args):
73        p = self.macros.expand('%{_sbdir}')
74        for arg in args:
75            p = path.join(p, arg)
76        return os.path.abspath(path.host(p))
77
78    def output(self, text):
79        self.out += '%s\n' % (text)
80
81    def is_text(self):
82        return self.format == 'text'
83
84    def is_asciidoc(self):
85        return self.format == 'asciidoc' or self.format == 'html'
86
87    def setup(self):
88        if self.format == 'html':
89            try:
90                import asciidocapi
91            except:
92                raise error.general('installation error: no asciidocapi found')
93            asciidoc_py = self._sbpath(options.basepath, 'asciidoc', 'asciidoc.py')
94            try:
95                self.asciidoc = asciidocapi.AsciiDocAPI(asciidoc_py)
96            except:
97                raise error.general('application error: asciidocapi failed')
98
99    def header(self):
100        pass
101
102    def footer(self):
103        pass
104
105    def git_status(self):
106        text = 'RTEMS Source Builder Repository Status'
107        if self.is_asciidoc():
108            self.output('')
109            self.output("'''")
110            self.output('')
111            self.output('.%s' % (text))
112        else:
113            self.output('-' * self.line_len)
114            self.output('%s' % (text))
115        repo = git.repo('.', self.opts, self.macros)
116        repo_valid = repo.valid()
117        if repo_valid:
118            if self.is_asciidoc():
119                self.output('*Remotes*:;;')
120            else:
121                self.output(' Remotes:')
122            repo_remotes = repo.remotes()
123            rc = 0
124            for r in repo_remotes:
125                rc += 1
126                if 'url' in repo_remotes[r]:
127                    text = repo_remotes[r]['url']
128                else:
129                    text = 'no URL found'
130                text = '%s: %s' % (r, text)
131                if self.is_asciidoc():
132                    self.output('. %s' % (text))
133                else:
134                    self.output('  %2d: %s' % (rc, text))
135            if self.is_asciidoc():
136                self.output('*Status*:;;')
137            else:
138                self.output(' Status:')
139            if repo.clean():
140                if self.is_asciidoc():
141                    self.output('Clean')
142                else:
143                    self.output('  Clean')
144            else:
145                if self.is_asciidoc():
146                    self.output('_Repository is dirty_')
147                else:
148                    self.output('  Repository is dirty')
149            repo_head = repo.head()
150            if self.is_asciidoc():
151                self.output('*Head*:;;')
152                self.output('Commit: %s' % (repo_head))
153            else:
154                self.output(' Head:')
155                self.output('  Commit: %s' % (repo_head))
156        else:
157            self.output('_Not a valid GIT repository_')
158        if self.is_asciidoc():
159            self.output('')
160            self.output("'''")
161            self.output('')
162
163    def introduction(self, name, intro_text):
164        if self.is_asciidoc():
165            h = 'RTEMS Source Builder Report'
166            self.output(h)
167            self.output('=' * len(h))
168            self.output(':doctype: book')
169            self.output(':toc2:')
170            self.output(':toclevels: 5')
171            self.output(':icons:')
172            self.output(':numbered:')
173            self.output(':data-uri:')
174            self.output('')
175            self.output('RTEMS Tools Project <rtems-users@rtems.org>')
176            self.output(datetime.datetime.now().ctime())
177            self.output('')
178            image = self._sbpath(options.basepath, 'images', 'rtemswhitebg.jpg')
179            self.output('image:%s["RTEMS",width="20%%"]' % (image))
180            self.output('')
181            if intro_text:
182                self.output('%s' % ('\n'.join(intro_text)))
183        else:
184            self.output('=' * self.line_len)
185            self.output('RTEMS Tools Project <rtems-users@rtems.org> %s' % datetime.datetime.now().ctime())
186            if intro_text:
187                self.output('')
188                self.output('%s' % ('\n'.join(intro_text)))
189            self.output('=' * self.line_len)
190            self.output('Report: %s' % (name))
191        self.git_status()
192
193    def config_start(self, name):
194        first = not self.configs_active
195        self.configs_active = True
196
197    def config_end(self, name):
198        if self.is_asciidoc():
199            self.output('')
200            self.output("'''")
201            self.output('')
202
203    def buildset_start(self, name):
204        if self.is_asciidoc():
205            h = '%s' % (name)
206            self.output('=%s %s' % ('=' * self.bset_nesting, h))
207        else:
208            self.output('=-' * (self.line_len / 2))
209            self.output('Build Set: %s' % (name))
210
211    def buildset_end(self, name):
212        self.configs_active = False
213
214    def source(self, package, source_tag):
215        return package.sources()
216
217    def patch(self, package, args):
218        return package.patches()
219
220    def output_info(self, name, info, separated = False):
221        if info is not None:
222            end = ''
223            if self.is_asciidoc():
224                if separated:
225                    self.output('*%s:*::' % (name))
226                    self.output('')
227                else:
228                    self.output('*%s:* ' % (name))
229                    end = ' +'
230                spaces = ''
231            else:
232                self.output(' %s:' % (name))
233                spaces = '  '
234            for l in info:
235                self.output('%s%s%s' % (spaces, l, end))
236            if self.is_asciidoc() and separated:
237                self.output('')
238
239    def output_directive(self, name, directive):
240        if directive is not None:
241            if self.is_asciidoc():
242                self.output('')
243                self.output('*%s*:' % (name))
244                self.output('--------------------------------------------')
245                spaces = ''
246            else:
247                self.output(' %s:' % (name))
248                spaces = '  '
249            for l in directive:
250                self.output('%s%s' % (spaces, l))
251            if self.is_asciidoc():
252                self.output('--------------------------------------------')
253
254    def config(self, configname, opts, macros):
255
256        _config = config.file(configname, opts, macros)
257        packages = _config.packages()
258        package = packages['main']
259        name = package.name()
260        self.config_start(name)
261        if self.is_asciidoc():
262            self.output('*Package*: _%s_ +' % (name))
263            self.output('*Config*: %s' % (configname))
264            self.output('')
265        else:
266            self.output('-' * self.line_len)
267            self.output('Package: %s' % (name))
268            self.output(' Config: %s' % (configname))
269        self.output_info('Summary', package.get_info('summary'), True)
270        self.output_info('URL', package.get_info('url'))
271        self.output_info('Version', package.get_info('version'))
272        self.output_info('Release', package.get_info('release'))
273        self.output_info('Build Arch', package.get_info('buildarch'))
274        if self.is_asciidoc():
275            self.output('')
276        sources = package.sources()
277        if self.is_asciidoc():
278            self.output('*Sources:*::')
279            if len(sources) == 0:
280                self.output('No sources')
281        else:
282            self.output('  Sources: %d' % (len(sources)))
283        c = 0
284        for s in sources:
285            c += 1
286            if self.is_asciidoc():
287                self.output('. %s' % (sources[s][0]))
288            else:
289                self.output('   %2d: %s' % (c, sources[s][0]))
290        patches = package.patches()
291        if self.is_asciidoc():
292            self.output('')
293            self.output('*Patches:*::')
294            if len(patches) == 0:
295                self.output('No patches')
296        else:
297            self.output('  Patches: %s' % (len(patches)))
298        c = 0
299        for p in patches:
300            c += 1
301            if self.is_asciidoc():
302                self.output('. %s' % (patches[p][0]))
303            else:
304                self.output('   %2d: %s' % (c, patches[p][0]))
305        self.output_directive('Preparation', package.prep())
306        self.output_directive('Build', package.build())
307        self.output_directive('Install', package.install())
308        self.output_directive('Clean', package.clean())
309        self.config_end(name)
310
311    def buildset(self, name):
312        self.bset_nesting += 1
313        self.buildset_start(name)
314        opts = copy.copy(self.opts)
315        macros = copy.copy(self.macros)
316        bset = setbuilder.buildset(name, self.configs, opts, macros)
317        for c in bset.load():
318            if c.endswith('.bset'):
319                self.buildset(c)
320            elif c.endswith('.cfg'):
321                self.config(c, opts, macros)
322            else:
323                raise error.general('invalid config type: %s' % (c))
324        self.buildset_end(name)
325        self.bset_nesting -= 1
326
327    def generate(self, name):
328        if self.format == 'html':
329            if self.asciidoc is None:
330                raise error.general('asciidoc not initialised')
331            import StringIO
332            infile = StringIO.StringIO(self.out)
333            outfile = StringIO.StringIO()
334            self.asciidoc.execute(infile, outfile)
335            self.out = outfile.getvalue()
336            infile.close()
337            outfile.close()
338        try:
339            o = open(name, "w")
340            o.write(self.out)
341            o.close()
342            del o
343        except IOError, err:
344            raise error.general('writing output file: %s: %s' % (name, err))
345
346    def make(self, inname, outname, intro_text = None):
347        self.setup()
348        self.introduction(inname, intro_text)
349        config = build.find_config(inname, self.configs)
350        if config is None:
351            raise error.general('config file not found: %s' % (inname))
352        if config.endswith('.bset'):
353            self.buildset(config)
354        elif config.endswith('.cfg'):
355            self.config(config, self.opts, self.macros)
356        else:
357            raise error.general('invalid config type: %s' % (config))
358        self.generate(outname)
359
360def run(args):
361    try:
362        optargs = { '--list-bsets':   'List available build sets',
363                    '--list-configs': 'List available configurations',
364                    '--format':       'Output format (text, html, asciidoc)',
365                    '--output':       'File name to output the report' }
366        opts = options.load(args, optargs)
367        log.default = log.log(opts.logfiles())
368        if opts.get_arg('--output') and len(opts.params()) > 1:
369            raise error.general('--output can only be used with a single config')
370        print 'RTEMS Source Builder, Reporter v%s' % (version.str())
371        if not check.host_setup(opts):
372            _notice(opts, 'warning: forcing build with known host setup problems')
373        configs = build.get_configs(opts)
374        if not setbuilder.list_bset_cfg_files(opts, configs):
375            output = opts.get_arg('--output')
376            if output is not None:
377                output = output[1]
378            format = 'text'
379            ext = '.txt'
380            format_opt = opts.get_arg('--format')
381            if format_opt:
382                if len(format_opt) != 2:
383                    raise error.general('invalid format option: %s' % ('='.join(format_opt)))
384                if format_opt[1] == 'text':
385                    pass
386                elif format_opt[1] == 'asciidoc':
387                    format = 'asciidoc'
388                    ext = '.txt'
389                elif format_opt[1] == 'html':
390                    format = 'html'
391                    ext = '.html'
392                else:
393                    raise error.general('invalid format: %s' % (format_opt[1]))
394            r = report(format, configs, opts)
395            for _config in opts.params():
396                if output is None:
397                    outname = path.splitext(_config)[0] + ext
398                    outname = outname.replace('/', '-')
399                else:
400                    outname = output
401                r.make(_config, outname)
402            del r
403    except error.general, gerr:
404        print gerr
405        sys.exit(1)
406    except error.internal, ierr:
407        print ierr
408        sys.exit(1)
409    except error.exit, eerr:
410        pass
411    except KeyboardInterrupt:
412        _notice(opts, 'abort: user terminated')
413        sys.exit(1)
414    sys.exit(0)
415
416if __name__ == "__main__":
417    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.