source: rtems-source-builder/source-builder/sb/reports.py @ 4727c3e

5
Last change on this file since 4727c3e was 4727c3e, checked in by Gedare Bloom <gedare@…>, on 04/02/20 at 04:29:31

sb/reports: add sanitize parameter enabled for --mail

Adds a --sanitize option to command line for reports.py
and also for the reports.report() interface from setbuilder.py
to remove the Remotes information from git.

Closes #3887.

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