source: rtems-source-builder/source-builder/sb/reports.py @ 5f7c53a

5
Last change on this file since 5f7c53a was 5f7c53a, checked in by Chris Johns <chrisj@…>, on 11/17/19 at 05:39:43

sb: Align the version processing with rtems-tools.

  • Use the same VERSION file format as rtems-tools so a common release generation can be used.
  • The version.py is almost the same as rtems-tools. There are some minor differences, one is the RTEMS version is present in this file while rtems-tool uses config/rtems-release.ini.

Updates #3822

  • Property mode set to 100644
File size: 33.9 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2010-2015 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
25from __future__ import print_function
26
27import base64
28import copy
29import datetime
30import os
31import sys
32
33import pprint
34pp = pprint.PrettyPrinter(indent = 2)
35
36try:
37    import build
38    import check
39    import config
40    import error
41    import git
42    import log
43    import options
44    import path
45    import setbuilder
46    import sources
47    import version
48except KeyboardInterrupt:
49    print('user terminated', file = sys.stderr)
50    sys.exit(1)
51except:
52    print('error: unknown application load error', file = sys.stderr)
53    sys.exit(1)
54
55_line_len = 78
56
57_title = 'RTEMS Tools Project <users@rtems.org>'
58
59_release_status_text = 'RTEMS Source Builder Release'
60_git_status_text = 'RTEMS Source Builder Repository Status'
61
62def _make_path(p, *args):
63    for arg in args:
64        p = path.join(p, arg)
65    return os.path.abspath(path.host(p))
66
67def platform(mode = 'all'):
68    import platform
69    if mode == 'system':
70        return platform.system()
71    compact = platform.platform(aliased = True)
72    if mode == 'compact':
73        return compact
74    extended = ' '.join(platform.uname())
75    if mode == 'extended':
76        return extended
77    return '%s (%s)' % (short, extended)
78
79class formatter(object):
80    def __init__(self):
81        self.content = ''
82
83    def line(self, text):
84        self.content += text + '\n'
85
86    def add(self, text):
87        self.content += text
88
89    def set_sbpath(self, sbpath):
90        self.sbpath = sbpath
91
92    def format(self):
93        raise error.general('internal error: formatter.format() not implemented')
94
95    def ext(self):
96        raise error.general('internal error: formatter.ext() not implemented')
97
98    def introduction(self, name, now, intro_text):
99        self.line('=' * _line_len)
100        self.line('%s %s' % (_title, now))
101        if intro_text:
102            self.line('')
103            self.line('%s' % ('\n'.join(intro_text)))
104        self.line('=' * _line_len)
105        self.line('Report: %s' % (name))
106
107    def epilogue(self, name):
108        return
109
110    def config_start(self, nest_level, name):
111        return
112
113    def config(self, nest_level, name, _config):
114        self.line('-' * _line_len)
115        self.line('Package: %s' % (name))
116        self.line(' Config: %s' % (_config.file_name()))
117
118    def config_end(self, nest_level, name):
119        return
120
121    def buildset_start(self, nest_level, name):
122        self.line('=-' * int(_line_len / 2))
123        self.line('Build Set: (%d) %s' % (nest_level, name))
124
125    def buildset_end(self, nest_level, name):
126        return
127
128    def info(self, nest_level, name, info, separated):
129        self.line(' ' + name + ':')
130        for l in info:
131            self.line('  ' + l)
132
133    def directive(self, nest_level, name, data):
134        self.line(' %s:' % (name))
135        for l in data:
136            self.line('  ' + l)
137
138    def files(self, nest_level, sigular, plural, _files):
139        self.line('  ' + plural + ': %d' % (len(_files)))
140        i = 0
141        for name in _files:
142            for s in _files[name]:
143                i += 1
144                self.line('   %2d: %s' % (i, s[0]))
145                if s[1] is None:
146                    h = 'No checksum'
147                else:
148                    hash = s[1].split()
149                    h = '%s: %s' % (hash[0], hash[1])
150                self.line('       %s' % (h))
151
152    def post_process(self):
153        return self.content
154
155class markdown_formatter(formatter):
156    def __init__(self):
157        super(markdown_formatter, self).__init__()
158        self.level_current = 1
159        self.level_path = '0.'
160        self.levels = { '0.': 0 }
161        self.cols = [20, 55]
162
163    def _heading(self, heading, level):
164        return '%s %s' % ('#' * level, heading)
165
166    def _strong(self, s):
167        return '__' + s + '__'
168
169    def _bold(self, s):
170        return '__' + s + '__'
171
172    def _italic(self, s):
173        return '_' + s + '_'
174
175    def _table_line(self):
176        l = '|'
177        for c in self.cols:
178            l += '-' * c + '|'
179        return l
180
181    def _table_row(self, cols):
182        if len(cols) != len(self.cols):
183            raise error.general('invalid table column count')
184        l = '|'
185        for c in range(0, len(cols)):
186            l += '%-*s|' % (self.cols[c], cols[c])
187        return l
188
189    def _btext(self, level, text):
190        return '> ' * (level - 1) + text
191
192    def _bline(self, level, text):
193        self.line(self._btext(level, text))
194
195    def _level(self, nest_level):
196        if nest_level > self.level_current:
197            self.level_path += '%d.' % (self.levels[self.level_path])
198        if nest_level < self.level_current:
199            self.level_path = self.level_path[:-2]
200        if self.level_path not in self.levels:
201            self.levels[self.level_path] = 0
202        self.level_current = nest_level
203        self.levels[self.level_path] += 1
204        return '%s%d.' % (self.level_path[2:], self.levels[self.level_path])
205
206    def format(self):
207        return 'markdown'
208
209    def ext(self):
210        return '.md'
211
212    def introduction(self, name, now, intro_text):
213        self.line('- - -')
214        self.line(self._heading('RTEMS Source Builder Report', 1))
215        self.line(self._strong(_title))
216        self.line('')
217        self.line(self._bold('Generated: ' + now))
218        self.line('')
219        if intro_text:
220            self.line('%s' % ('\n'.join(intro_text)))
221            self.line('')
222        self.line('')
223        self.line('- - -')
224        self.line(self._heading('Table Of Contents', 2))
225        self.line('')
226        self.line('[TOC]')
227        self.line('')
228
229    def release_status(self, release_string):
230        self.line('')
231        self.line(self._heading(_release_status_text, 2))
232        self.line('')
233        self.line('*Version*: %s;;' % (release_string))
234        self.line('')
235
236    def git_status(self, valid, dirty, head, remotes):
237        self.line('')
238        self.line('- - -')
239        self.line(self._heading(_git_status_text, 2))
240        if valid:
241            self.line(self._strong('Remotes:'))
242            self.line('')
243            rc = 1
244            for r in remotes:
245                if 'url' in remotes[r]:
246                    text = remotes[r]['url']
247                else:
248                    text = 'no URL found'
249                self.line('%d. %s: %s' % (rc, r, text))
250                rc += 1
251            self.line('')
252            self.line(self._strong('Status:'))
253            self.line('')
254            if dirty:
255                self.line('> ' + self._italic('Repository is dirty'))
256            else:
257                self.line('> Clean')
258            self.line('>')
259            self.line('> ' + self._bold('Head: ') + head)
260        else:
261            self.line('> ' + self._italic('Not a valid GIT repository'))
262        self.line('')
263
264    def config(self, nest_level, name, _config):
265        self._bline(nest_level, self._bold('Package:'))
266        self._bline(nest_level, '')
267        self._bline(nest_level + 1, self._table_row([self._bold('Item'),
268                                                     self._bold('Description')]))
269        self._bline(nest_level + 1, self._table_line())
270        self._bline(nest_level + 1, self._table_row(['Package', name]))
271        self._bline(nest_level + 1, self._table_row(['Config',
272                                                     _config.file_name()]))
273
274    def config_end(self, nest_level, name):
275        self._bline(nest_level + 1, '')
276
277    def buildset_start(self, nest_level, name):
278        if nest_level == 1:
279            self.line('- - -')
280            self._bline(nest_level,
281                        self._heading('RTEMS Source Builder Packages', 2))
282        self._bline(nest_level,
283                    self._heading('%s Build %s' % (self._level(nest_level), name), 3))
284
285    def info(self, nest_level, name, info, separated):
286        self._bline(nest_level + 1,
287                    self._table_row([name, ' '.join(info)]))
288
289    def directive(self, nest_level, name, data):
290        self._bline(nest_level, '')
291        self._bline(nest_level, self._bold(name + ':'))
292        for l in data:
293            self._bline(nest_level + 1, ' ' * 4 + l)
294
295    def files(self, nest_level, singular, plural, _files):
296        self._bline(nest_level, '')
297        self._bline(nest_level, self._bold(plural + ':'))
298        self._bline(nest_level, '')
299        if len(_files) == 0:
300            self._bline(nest_level + 1, 'No ' + plural.lower())
301        fc = 0
302        for name in _files:
303            for s in _files[name]:
304                fc += 1
305                if s[1] is None:
306                    h = self._bold('No checksum')
307                else:
308                    hash = s[1].split()
309                    h = '%s: %s' % (hash[0], hash[1])
310                self._bline(nest_level,
311                            '%d. [%s](%s "%s %s")<br/>' % (fc, s[0], s[0],
312                                                           name, singular.lower()))
313                self._bline(nest_level,
314                            '    <span class=checksum>%s</span>' % (h))
315
316class html_formatter(markdown_formatter):
317    def __init__(self):
318        super(html_formatter, self).__init__()
319        self.html_header = '<!DOCTYPE html>' + os.linesep + \
320                           '<html lang="en">' + os.linesep + \
321                           '<head>' + os.linesep + \
322                           '<title>RTEMS RSB - @BUILD@</title>' + os.linesep + \
323                           '<meta http-equiv="content-type" content="text/html; charset=UTF-8" />' + os.linesep + \
324                           '<meta name="created" content="@NOW@" />' + os.linesep + \
325                           '<meta name="description" content="RTEMS RSB Report" />' + os.linesep + \
326                           '<meta name="keywords" content="RTEMS RSB" />' + os.linesep + \
327                           '<meta charset="utf-8">' + os.linesep + \
328                           '<meta http-equiv="X-UA-Compatible" content="IE=edge">' + os.linesep + \
329                           '<meta name="viewport" content="width=device-width, initial-scale=1">' + os.linesep + \
330                           '<style type="text/css">' + os.linesep + \
331                           'body {' + os.linesep + \
332                           ' font-family: arial, helvetica, serif;' + os.linesep + \
333                           ' font-style: normal;' + os.linesep + \
334                           ' font-weight: 400;' + os.linesep + \
335                           '}' + os.linesep + \
336                           'h1, h2 { margin: 10px 5px 10px 5px; }' + os.linesep + \
337                           'h1 { font-size: 28px; }' + os.linesep + \
338                           'h2 { font-size: 22px;}' + os.linesep + \
339                           'h3 { font-size: 18px; }' + os.linesep + \
340                           'p, ol, blockquote, h3, table, pre { margin: 1px 20px 2px 7px; }' + os.linesep + \
341                           'table, th, td, pre { border: 1px solid gray; border-spacing: 0px; }' + os.linesep + \
342                           'table { width: 100%; }' + os.linesep + \
343                           'th, td { padding: 1px; }' + os.linesep + \
344                           'pre { padding: 4px; }' + os.linesep + \
345                           '.checksum { font-size: 12px; }' + os.linesep + \
346                           '</style>' + os.linesep + \
347                           '</head>' + os.linesep + \
348                           '<body>' + os.linesep
349        self.html_footer = '</body>' + os.linesep + \
350                           '</html>' + os.linesep
351
352    def _logo(self):
353        logo = _make_path(self.sbpath, options.basepath, 'images', 'rtemswhitebg.jpg')
354        try:
355            with open(logo, "rb") as image:
356                b64 = base64.b64encode(image.read())
357        except:
358            raise error.general('installation error: no logo found')
359        logo = '<img alt="RTEMS Project" height="100" src="data:image/png;base64,' + b64 + '" />'
360        return logo
361
362    def format(self):
363        return 'html'
364
365    def ext(self):
366        return '.html'
367
368    def introduction(self, name, now, intro_text):
369        self.name = name
370        self.now = now
371        super(html_formatter, self).introduction(name, now, intro_text)
372
373    def post_process(self):
374        try:
375            import markdown
376        except:
377            raise error.general('installation error: no markdown found')
378        try:
379            out = markdown.markdown(self.content,
380                                    output_format = 'html5',
381                                    extensions = ['markdown.extensions.toc',
382                                                  'markdown.extensions.tables',
383                                                  'markdown.extensions.sane_lists',
384                                                  'markdown.extensions.smarty'])
385        except:
386            raise
387            raise error.general('application error: markdown failed')
388        header = self.html_header.replace('@BUILD@', self.name).replace('@NOW@', self.now)
389        footer = self.html_footer
390        logo = self._logo()
391        return header + logo + out + footer
392
393class text_formatter(formatter):
394    def __init__(self):
395        super(text_formatter, self).__init__()
396        self.cini = ''
397
398    def format(self):
399        return 'text'
400
401    def ext(self):
402        return '.txt'
403
404    def introduction(self, name, now, intro_text):
405        self.line('=' * _line_len)
406        self.line('%s %s' % (_title, now))
407        if intro_text:
408            self.line('')
409            self.line('%s' % ('\n'.join(intro_text)))
410        self.line('=' * _line_len)
411        self.line('Report: %s' % (name))
412
413    def release_status_header(self):
414        self.line('-' * _line_len)
415        self.line('%s' % (_release_status_text))
416
417    def release_status(self, release_string):
418        self.release_status_header()
419        self.line('%s Version: %s' % (self.cini, release_string))
420
421    def git_status_header(self):
422        self.line('-' * _line_len)
423        self.line('%s' % (_git_status_text))
424
425    def git_status(self, valid, dirty, head, remotes):
426        self.git_status_header()
427        if valid:
428            self.line('%s Remotes:' % (self.cini))
429            rc = 0
430            for r in remotes:
431                rc += 1
432                if 'url' in remotes[r]:
433                    text = remotes[r]['url']
434                else:
435                    text = 'no URL found'
436                text = '%s: %s' % (r, text)
437                self.line('%s  %2d: %s' % (self.cini, rc, text))
438            self.line('%s Status:' % (self.cini))
439            if dirty:
440                self.line('%s  Repository is dirty' % (self.cini))
441            else:
442                self.line('%s  Clean' % (self.cini))
443            self.line('%s Head:' % (self.cini))
444            self.line('%s  Commit: %s' % (self.cini, head))
445        else:
446            self.line('%s Not a valid GIT repository' % (self.cini))
447
448class ini_formatter(text_formatter):
449    def __init__(self):
450        super(ini_formatter, self).__init__()
451        self.cini = ';'
452        self.ini_pkg = {}
453        self.name = None
454
455    def format(self):
456        return 'ini'
457
458    def ext(self):
459        return '.ini'
460
461    def introduction(self, name, now, intro_text):
462        self.line(';')
463        self.line('; %s %s' % (_title, now))
464        if intro_text:
465            self.line(';')
466            self.line('; %s' % ('\n; '.join(intro_text)))
467            self.line(';')
468
469    def epilogue(self, name):
470        pkgs = sorted(self.ini_pkg.keys())
471        for pkg in pkgs:
472            self.line('')
473            self.line('[%s]' % (pkg))
474            items = sorted(self.ini_pkg[pkg].keys())
475            for item in items:
476                i = self.ini_pkg[pkg][item]
477                if len(i) == 1:
478                    self.line('%s = "%s"' % (item, i[0]))
479                else:
480                    self.line('%s = <<<DATA' % (item))
481                    self.line('\n'.join(i))
482                    self.line('DATA')
483        self.line('')
484
485    def release_status_header(self):
486        self.line(';')
487        self.line('; %s' % (_release_status_text))
488
489    def git_status_header(self):
490        self.line(';')
491        self.line('; %s' % (_git_status_text))
492        self.line(';')
493
494    def config(self, nest_level, name, _config):
495        pass
496
497    def buildset_start(self, nest_level, name):
498        if name.endswith('.cfg'):
499            self.name = path.basename(name[:-4])
500            if self.name not in self.ini_pkg:
501                self.ini_pkg[self.name] = {}
502
503    def buildset_end(self, nest_level, name):
504        self.name = None
505
506    def info(self, nest_level, name, info, separated):
507        if self.name:
508            if 'info' not in self.ini_pkg[self.name]:
509                self.ini_pkg[self.name]['info'] = []
510            self.ini_pkg[self.name]['info'] += info
511
512    def directive(self, nest_level, name, data):
513        if self.name:
514            if name not in self.ini_pkg[self.name]:
515                self.ini_pkg[self.name][name] = []
516            self.ini_pkg[self.name][name] += data
517
518    def files(self, nest_level, singular, plural, _files):
519        pass
520
521class xml_formatter(formatter):
522    def __init__(self):
523        super(xml_formatter, self).__init__()
524
525    def format(self):
526        return 'xml'
527
528    def ext(self):
529        return '.xml'
530
531    def introduction(self, name, now, intro_text):
532        self.line('<RTEMSSourceBuilderReport>')
533        if intro_text:
534            self.line('\t<Introduction>%s</Introduction>' % (intro_text))
535
536    def epilogue(self, name):
537        self.line('</RTEMSSourceBuilderReport>')
538
539    def release_status(self, release_string):
540        self.line('\t<Release>')
541        self.line('\t\t<Version>%s</Version>' % (release_string))
542        self.line('\t</Release>')
543
544    def git_status(self, valid, dirty, head, remotes):
545        self.line('\t<Git>')
546        if valid:
547            if dirty:
548                self.line('\t\t<Status>dirty</Status>')
549            else:
550                self.line('\t\t<Status>clean</Status>')
551            self.line('\t\t<Commit>' + head + '</Commit>')
552        else:
553            self.line('\t\t<Status>invalid</Status>')
554        self.line('\t</Git>')
555
556    def config_start(self, nest_level, name):
557        self.line('\t' * nest_level + '<Package name="' + name + '">')
558
559    def config(self, nest_level, name, _config):
560        self.line('\t' * nest_level + '<Config>' + _config.file_name() + '</Config>')
561
562    def config_end(self, nest_level, name):
563        self.line('\t' * nest_level + '</Package>')
564
565    def buildset_start(self, nest_level, name):
566        self.line('\t' * nest_level + '<BuildSet name="' + name + '">')
567
568    def buildset_end(self, nest_level, name):
569        self.line('\t' * nest_level + '</BuildSet>')
570
571    def info(self, nest_level, name, info, separated):
572        self.add('\t' * nest_level + '<' + name.replace(' ', '') + '>')
573        for l in info:
574            self.add(l)
575        self.line('</' + name + '>')
576
577    def directive(self, nest_level, name, data):
578        self.line('\t' * nest_level + '<' + name + '><![CDATA[')
579        for l in data:
580            self.line(l.replace(']]>', ']]]><![CDATA[]>'))
581        self.line(']]></' + name + '>')
582
583    def files(self, nest_level, sigular, plural, _files):
584        for name in _files:
585            for s in _files[name]:
586                self.add('\t' * nest_level + '<' + sigular)
587                if not (s[1] is None):
588                    hash = s[1].split()
589                    self.add(' ' + hash[0] + '="' + hash[1] + '"')
590                self.line('>' + s[0] + '</' + sigular + '>')
591
592def _tree_name(path_):
593    return path.splitext(path.basename(path_))[0]
594
595def _merge(_dict, new):
596    new = copy.deepcopy(new)
597    for i in new:
598        if i not in _dict:
599            _dict[i] = new[i]
600        else:
601            _dict[i] += new[i]
602
603class report:
604    """Report the build details about a package given a config file."""
605
606    def __init__(self, formatter, _configs, opts, macros = None):
607        if type(formatter) == str:
608            if formatter == 'text':
609                self.formatter = text_formatter()
610            elif formatter == 'markdown':
611                self.formatter = markdown_formatter()
612            elif formatter == 'html':
613                self.formatter = html_formatter()
614            elif formatter == 'ini':
615                self.formatter = ini_formatter()
616            elif formatter == 'xml':
617                self.formatter = xml_formatter()
618            else:
619                raise error.general('invalid format: %s' % (formatter))
620        else:
621            self.formatter = formatter
622        self.configs = _configs
623        self.opts = opts
624        if macros is None:
625            self.macros = opts.defaults
626        else:
627            self.macros = macros
628        self.sbpath = self.macros.expand('%{_sbdir}')
629        self.formatter.set_sbpath(self.sbpath)
630        self.bset_nesting = 0
631        self.out = ''
632        self.tree = {}
633        self.files = { 'buildsets':[], 'configs':[] }
634
635    def output(self, text):
636        self.formatter.line(text)
637
638    def is_ini(self):
639        return self.formatter.format() == 'ini'
640
641    def header(self):
642        pass
643
644    def footer(self):
645        pass
646
647    def release_status(self):
648        self.formatter.release_status(version.string())
649
650    def git_status(self):
651        r = git.repo('.', self.opts, self.macros)
652        self.formatter.git_status(r.valid(), r.dirty(), r.head(), r.remotes())
653
654    def introduction(self, name, intro_text = None):
655        now = datetime.datetime.now().ctime()
656        self.formatter.introduction(name, now, intro_text)
657        if version.released():
658            self.release_status()
659        else:
660            self.git_status()
661
662    def epilogue(self, name):
663        self.formatter.epilogue(name)
664
665    def config_start(self, name, _config):
666        self.files['configs'] += [name]
667        for cf in _config.includes():
668            cfbn = path.basename(cf)
669            if cfbn not in self.files['configs']:
670                self.files['configs'] += [cfbn]
671        self.formatter.config_start(self.bset_nesting + 1, name)
672
673    def config_end(self, name, _config):
674        self.formatter.config_end(self.bset_nesting + 1, name)
675
676    def buildset_start(self, name):
677        self.bset_nesting += 1
678        self.files['buildsets'] += [name]
679        self.formatter.buildset_start(self.bset_nesting, name)
680
681    def buildset_end(self, name):
682        self.formatter.buildset_end(self.bset_nesting, name)
683        self.bset_nesting -= 1
684
685    def source(self, macros):
686        def err(msg):
687            raise error.general('%s' % (msg))
688        _srcs = {}
689        for p in sources.get_source_names(macros, err):
690            if 'setup' in sources.get_source_keys(p, macros, err):
691                _srcs[p] = \
692                    [s for s in sources.get_sources(p, macros, err) if not s.startswith('%setup')]
693                _srcs[p] = [macros.expand(s) for s in _srcs[p]]
694        srcs = {}
695        for p in _srcs:
696            srcs[p] = [(s, sources.get_hash(path.basename(s).lower(), macros)) for s in _srcs[p]]
697        return srcs
698
699    def patch(self, macros):
700        def err(msg):
701            raise error.general('%s' % (msg))
702        _patches = {}
703        for n in sources.get_patch_names(macros, err):
704            if 'setup' in sources.get_patch_keys(n, macros, err):
705                _patches[n] = \
706                    [p for p in sources.get_patches(n, macros, err) if not p.startswith('%setup')]
707                _patches[n] = [macros.expand(p.split()[-1]) for p in _patches[n]]
708        patches = {}
709        for n in _patches:
710            patches[n] = [(p, sources.get_hash(path.basename(p).lower(), macros)) for p in _patches[n]]
711        return patches
712
713    def output_info(self, name, info, separated = False):
714        if info is not None:
715            self.formatter.info(self.bset_nesting + 2, name, info, separated)
716
717    def output_directive(self, name, directive):
718        if directive is not None:
719            self.formatter.directive(self.bset_nesting + 2, name, directive)
720
721    def tree_packages(self, tree, packages = []):
722        if 'bset' in tree:
723            for node in sorted(tree['bset'].keys()):
724                packages += [_tree_name(node)]
725                packages += self.tree_packages(tree['bset'][node], packages)
726        return set(packages)
727
728    def tree_sources(self, name, tree, sources = []):
729        if 'cfg' in tree:
730            packages = {}
731            if 'sources' in tree['cfg']:
732                _merge(packages, tree['cfg']['sources'])
733            if 'patches' in tree['cfg']:
734                _merge(packages, tree['cfg']['patches'])
735            for package in packages:
736                for source in packages[package]:
737                    if not source[0].startswith('git') and not source[0].startswith('cvs'):
738                        sources += [(path.basename(source[0]), source[0], source[1])]
739        if 'bset' in tree:
740            for node in sorted(tree['bset'].keys()):
741                self.tree_sources(_tree_name(node), tree['bset'][node], sources)
742        return sources
743
744    def config(self, _config, tree, opts, macros):
745        packages = _config.packages()
746        package = packages['main']
747        name = package.name()
748        if len(name) == 0:
749            return
750        sources = self.source(macros)
751        patches = self.patch(macros)
752        if tree is not None:
753            tree['file'] += [_config.file_name()]
754            if len(sources):
755                if 'sources' in tree:
756                    tree['sources'] = dict(list(tree['sources'].items()) + list(sources.items()))
757                else:
758                    tree['sources'] = sources
759            if len(patches):
760                if 'patches' in tree:
761                    tree['patches'] = dict(list(tree['patches'].items()) + list(patches.items()))
762                else:
763                    tree['patches'] = patches
764        self.config_start(name, _config)
765        self.formatter.config(self.bset_nesting + 2, name, _config)
766        self.output_info('Summary', package.get_info('summary'), True)
767        self.output_info('URL', package.get_info('url'))
768        self.output_info('Version', package.get_info('version'))
769        self.output_info('Release', package.get_info('release'))
770        self.output_info('Build Arch', package.get_info('buildarch'))
771        self.formatter.files(self.bset_nesting + 2, "Source", "Sources", sources)
772        self.formatter.files(self.bset_nesting + 2, "Patch", "Patches", patches)
773        self.output_directive('Preparation', package.prep())
774        self.output_directive('Build', package.build())
775        self.output_directive('Install', package.install())
776        self.output_directive('Clean', package.clean())
777        self.config_end(name, _config)
778
779    def generate_ini_tree(self, name, tree, prefix_char, prefix = ''):
780        if prefix_char == '|':
781            c = '|'
782        else:
783            c = '+'
784        self.output('; %s  %s- %s' % (prefix, c, name))
785        prefix += '  %s ' % (prefix_char)
786        if 'cfg' in tree:
787            files = sorted(tree['cfg']['file'])
788            if len(files):
789                for f in range(0, len(files) - 1):
790                    self.output('; %s  |- %s' % (prefix, files[f]))
791                if 'bset' in tree and len(list(tree['bset'].keys())):
792                    c = '|'
793                else:
794                    c = '+'
795                self.output('; %s  %s- %s' % (prefix, c, files[f + 1]))
796        if 'bset' in tree:
797            nodes = sorted(tree['bset'].keys())
798            for node in range(0, len(nodes)):
799                if node == len(nodes) - 1:
800                    prefix_char = ' '
801                else:
802                    prefix_char = '|'
803                self.generate_ini_tree(nodes[node],
804                                       tree['bset'][nodes[node]],
805                                       prefix_char,
806                                       prefix)
807
808    def generate_ini_source(self, sources):
809        self.output('')
810        self.output('[source]')
811        for source in sources:
812            self.output(' %s = %s' % (source[0], source[1]))
813
814    def generate_ini_hash(self, sources):
815        self.output('')
816        self.output('[hash]')
817        for source in sources:
818            if source[2] is None:
819                hash = ''
820            else:
821                hash = source[2].split()
822                hash = '%s:%s' % (hash[0], hash[1])
823            self.output(' %s = %s' % (source[0], hash))
824
825    def generate_ini(self):
826        nodes = sorted([node for node in list(self.tree.keys()) if node != 'bset'])
827        self.output(';')
828        self.output('; Configuration Tree:')
829        for node in range(0, len(nodes)):
830            if node == len(nodes) - 1:
831                prefix_char = ' '
832            else:
833                prefix_char = '|'
834            self.generate_ini_tree(nodes[node], self.tree[nodes[node]], prefix_char)
835        self.output(';')
836        sources = []
837        for node in nodes:
838            sources += self.tree_sources(_tree_name(node), self.tree[node])
839        sources = sorted(set(sources))
840        self.generate_ini_source(sources)
841        self.generate_ini_hash(sources)
842
843    def get_output(self):
844        return self.formatter.post_process()
845
846    def write(self, name):
847        self.out = self.formatter.post_process()
848        if name is not None:
849            try:
850                o = open(path.host(name), "w")
851                o.write(self.out)
852                o.close()
853                del o
854            except IOError as err:
855                raise error.general('writing output file: %s: %s' % (name, err))
856
857    def generate(self, name, tree = None, opts = None, macros = None):
858        self.buildset_start(name)
859        if tree is None:
860            tree = self.tree
861        if opts is None:
862            opts = self.opts
863        if macros is None:
864            macros = self.macros
865        bset = setbuilder.buildset(name, self.configs, opts, macros)
866        if name in tree:
867            raise error.general('duplicate build set in tree: %s' % (name))
868        tree[name] = { 'bset': { }, 'cfg': { 'file': []  } }
869        for c in bset.load():
870            macros = copy.copy(bset.macros)
871            if c.endswith('.bset'):
872                self.generate(c, tree[name]['bset'], bset.opts, macros)
873            elif c.endswith('.cfg'):
874                self.buildset_start(c)
875                self.config(config.file(c, bset.opts, macros),
876                            tree[name]['cfg'], bset.opts, macros)
877                self.buildset_end(c)
878            else:
879                raise error.general('invalid config type: %s' % (c))
880        self.buildset_end(name)
881
882    def create(self, inname, outname = None, intro_text = None):
883        self.introduction(inname, intro_text)
884        self.generate(inname)
885        if self.is_ini():
886            self.generate_ini()
887        self.epilogue(inname)
888        self.write(outname)
889
890def run(args):
891    try:
892        optargs = { '--list-bsets':   'List available build sets',
893                    '--list-configs': 'List available configurations',
894                    '--format':       'Output format (text, html, markdown, ini, xml)',
895                    '--output':       'File name to output the report' }
896        opts = options.load(args, optargs, logfile = False)
897        if opts.get_arg('--output') and len(opts.params()) > 1:
898            raise error.general('--output can only be used with a single config')
899        print('RTEMS Source Builder, Reporter, %s' % (version.string()))
900        opts.log_info()
901        if not check.host_setup(opts):
902            log.warning('forcing build with known host setup problems')
903        configs = build.get_configs(opts)
904        if not setbuilder.list_bset_cfg_files(opts, configs):
905            output = opts.get_arg('--output')
906            if output is not None:
907                output = output[1]
908            formatter = text_formatter()
909            format_opt = opts.get_arg('--format')
910            if format_opt:
911                if len(format_opt) != 2:
912                    raise error.general('invalid format option: %s' % ('='.join(format_opt)))
913                if format_opt[1] == 'text':
914                    pass
915                elif format_opt[1] == 'markdown':
916                    formatter = markdown_formatter()
917                elif format_opt[1] == 'html':
918                    formatter = html_formatter()
919                elif format_opt[1] == 'ini':
920                    formatter = ini_formatter()
921                elif format_opt[1] == 'xml':
922                    formatter = xml_formatter()
923                else:
924                    raise error.general('invalid format: %s' % (format_opt[1]))
925            r = report(formatter, configs, opts)
926            for _config in opts.params():
927                if output is None:
928                    outname = path.splitext(_config)[0] + formatter.ext()
929                    outname = outname.replace('/', '-')
930                else:
931                    outname = output
932                config = build.find_config(_config, configs)
933                if config is None:
934                    raise error.general('config file not found: %s' % (_config))
935                r.create(config, outname)
936            del r
937        else:
938            raise error.general('invalid config type: %s' % (config))
939    except error.general as gerr:
940        print(gerr)
941        sys.exit(1)
942    except error.internal as ierr:
943        print(ierr)
944        sys.exit(1)
945    except error.exit as eerr:
946        pass
947    except KeyboardInterrupt:
948        log.notice('abort: user terminated')
949        sys.exit(1)
950    sys.exit(0)
951
952if __name__ == "__main__":
953    run(sys.argv)
Note: See TracBrowser for help on using the repository browser.