diff --git a/source-builder/sb/defaults.py b/source-builder/sb/defaults.py index 2d51549..1497bbd 100644 --- a/source-builder/sb/defaults.py +++ b/source-builder/sb/defaults.py @@ -119,6 +119,7 @@ defaults = { '__cpp': ('exe', 'none', '%{__cc} -E'), '__cxx': ('exe', 'required', '/usr/bin/g++'), '__flex': ('exe', 'required', '/usr/bin/flex'), +'__git': ('exe', 'required', '/usr/bin/git'), '__grep': ('exe', 'required', '/usr/bin/grep'), '__gzip': ('exe', 'required', '/usr/bin/gzip'), '__id': ('exe', 'required', '/usr/bin/id'), @@ -401,8 +402,10 @@ class command_line: self.defaults[_arch] = ('none', 'none', _arch_value) self.defaults[_vendor] = ('none', 'none', _vendor_value) self.defaults[_os] = ('none', 'none', _os_value) - if not lo and a not in self.optargs: - raise error.general('invalid argument (try --help): %s' % (a)) + if not lo: + sa = a.split('=') + if sa[0] not in self.optargs: + raise error.general('invalid argument (try --help): %s' % (a)) else: if a == '-f': self.opts['force'] = '1' @@ -477,8 +480,9 @@ class command_line: if not arg in self.optargs: raise error.internal('bad arg: %s' % (arg)) for a in self.args: - if a.startswith(arg): - return a + sa = a.split('=') + if sa[0].startswith(arg): + return sa return None def get_config_files(self, config): diff --git a/source-builder/sb/freebsd.py b/source-builder/sb/freebsd.py index 25b320f..c5569bd 100644 --- a/source-builder/sb/freebsd.py +++ b/source-builder/sb/freebsd.py @@ -59,6 +59,7 @@ def load(): '_smp_mflags': ('none', 'none', smp_mflags), '__bash': ('exe', 'optional', '/usr/local/bin/bash'), '__bison': ('exe', 'required', '/usr/local/bin/bison'), + '__git': ('exe', 'required', '/usr/local/bin/git'), '__xz': ('exe', 'optional', '/usr/bin/xz'), '__make': ('exe', 'required', 'gmake') } diff --git a/source-builder/sb/git.py b/source-builder/sb/git.py new file mode 100644 index 0000000..b012391 --- /dev/null +++ b/source-builder/sb/git.py @@ -0,0 +1,130 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2013 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# +# Provide some basic access to the git command. +# + +import os + +import defaults +import error +import execute +import path + +class repo: + """An object to manage a git repo.""" + + def _git_exit_code(self, ec): + if ec: + raise error.general('git command failed (%s): %d' % (self.git, ec)) + + def _run(self, args, check = False): + e = execute.capture_execution() + exit_code, proc, output = e.spawn([self.git] + args) + if check: + self._git_exit_code(exit_code) + return exit_code, output + + def __init__(self, _path, _opts, _defaults): + self.path = _path + self.opts = _opts + self.default = _defaults + self.git = _opts.expand('%{__git}', _defaults) + + def git_version(self): + ec, output = self._run(['--version'], True) + gvs = output.split() + if len(gvs) < 3: + raise error.general('invalid version string from git: %s' % (output)) + vs = gvs[2].split('.') + if len(vs) != 4: + raise error.general('invalid version number from git: %s' % (gvs[2])) + return (int(vs[0]), int(vs[1]), int(vs[2]), int(vs[3])) + + def status(self): + _status = {} + ec, output = self._run(['status']) + if ec == 0: + state = 'none' + for l in output.split('\n'): + if l.startswith('# On branch '): + _status['branch'] = l[len('# On branch '):] + elif l.startswith('# Changes to be committed:'): + state = 'staged' + elif l.startswith('# Changes not staged for commit:'): + state = 'unstaged' + elif l.startswith('# Untracked files:'): + state = 'untracked' + elif state != 'none' and l[0] == '#': + if l.strip() != '#' and not l.startswith('# ('): + if state not in _status: + _status[state] = [] + l = l[1:] + if ':' in l: + l = l.split(':')[1] + _status[state] += [l.strip()] + return _status + + def clean(self): + _status = self.status() + return len(_status) == 1 and 'branch' in _status + + def valid(self): + ec, output = self._run(['status']) + return ec == 0 + + def remotes(self): + _remotes = {} + ec, output = self._run(['config', '--list']) + if ec == 0: + for l in output.split('\n'): + if l.startswith('remote'): + ls = l.split('=') + if len(ls) >= 2: + rs = ls[0].split('.') + if len(rs) == 3: + r_name = rs[1] + r_type = rs[2] + if r_name not in _remotes: + _remotes[r_name] = {} + if r_type not in _remotes[r_name]: + _remotes[r_name][r_type] = [] + _remotes[r_name][r_type] = '='.join(ls[1:]) + return _remotes + + def head(self): + hash = '' + ec, output = self._run(['log', '-n', '1']) + if ec == 0: + l1 = output.split('\n')[0] + if l1.startswith('commit '): + hash = l1[len('commit '):] + return hash + +if __name__ == '__main__': + import sys + _opts, _defaults = defaults.load(sys.argv) + g = repo('.', _opts, _defaults) + print g.git_version() + print g.valid() + print g.status() + print g.clean() + print g.remotes() + print g.head() diff --git a/source-builder/sb/reports.py b/source-builder/sb/reports.py index 2746b37..a2917bc 100644 --- a/source-builder/sb/reports.py +++ b/source-builder/sb/reports.py @@ -22,16 +22,26 @@ # installed not to be package unless you run a packager around this. # +import datetime import os import sys -import build -import check -import config -import defaults -import error -import log -import setbuilder +try: + import build + import check + import config + import defaults + import error + import git + import log + import path + import setbuilder +except KeyboardInterrupt: + print 'user terminated' + sys.exit(1) +except: + print 'unknown application load error' + sys.exit(1) # # Version of Sourcer Builder Build. @@ -49,18 +59,18 @@ class report: line_len = 78 - def __init__(self, name, format, _configs, _defaults, opts): + def __init__(self, format, _configs, _defaults, opts): self.format = format - self.name = name self.configs = _configs self.defaults = _defaults self.opts = opts self.bset_nesting = 0 self.configs_active = False + self.out = '' + self.asciidoc = None - def _output(self, text): - if not self.opts.quiet(): - log.output(text) + def output(self, text): + self.out += '%s\n' % (text) def is_text(self): return self.format == 'text' @@ -70,7 +80,14 @@ class report: def setup(self): if self.is_asciidoc(): - pass + try: + import asciidocapi + except: + raise error.general('installation error: no asciidocapi found') + try: + self.asciidoc = asciidocapi.AsciiDocAPI() + except: + raise error.general('application error: asciidocapi failed') def header(self): pass @@ -78,46 +95,118 @@ class report: def footer(self): pass - def introduction(self, name): + def git_status(self): + text = 'RTEMS Source Builder Repository Status' + if self.is_asciidoc(): + self.output('') + self.output("'''") + self.output('') + self.output('.%s' % (text)) + else: + self.output('-' * self.line_len) + self.output('%s' % (text)) + repo = git.repo('.', self.opts, self.defaults) + repo_valid = repo.valid() + if repo_valid: + if self.is_asciidoc(): + self.output('*Remotes*:;;') + else: + self.output(' Remotes:') + repo_remotes = repo.remotes() + rc = 0 + for r in repo_remotes: + rc += 1 + if 'url' in repo_remotes[r]: + text = repo_remotes[r]['url'] + else: + text = 'no URL found' + text = '%s: %s' % (r, text) + if self.is_asciidoc(): + self.output('. %s' % (text)) + else: + self.output(' %2d: %s' % (rc, text)) + if self.is_asciidoc(): + self.output('*Status*:;;') + else: + self.output(' Status:') + if repo.clean(): + if self.is_asciidoc(): + self.output('Clean') + else: + self.output(' Clean') + else: + if self.is_asciidoc(): + self.output('_Repository is dirty_') + else: + self.output(' Repository is dirty') + repo_head = repo.head() + if self.is_asciidoc(): + self.output('*Head*:;;') + self.output('Commit: %s' % (repo_head)) + else: + self.output(' Head:') + self.output(' Commit: %s' % (repo_head)) + else: + self.output('_Not a valid GIT repository_') + if self.is_asciidoc(): + self.output('') + self.output("'''") + self.output('') + + def introduction(self, name, intro_text): if self.is_asciidoc(): h = 'RTEMS Source Builder Report' - log.output(h) - log.output('=' * len(h)) - log.output(':doctype: book') - log.output(':toc2:') - log.output(':toclevels: 5') - log.output(':icons:') - log.output(':numbered:') - log.output(' ') - log.output('RTEMS Project ') - log.output('28th Feb 2013') - log.output(' ') + self.output(h) + self.output('=' * len(h)) + self.output(':doctype: book') + self.output(':toc2:') + self.output(':toclevels: 5') + self.output(':icons:') + self.output(':numbered:') + self.output(':data-uri:') + self.output('') + self.output('RTEMS Tools Project ') + self.output(datetime.datetime.now().ctime()) + self.output('') + image = os.path.abspath(path.host(path.join(self.opts.expand('%{_sbdir}', self.defaults), + 'sb', 'images', 'rtemswhitebg.jpg'))) + self.output('image:%s["RTEMS",width="20%%"]' % (image)) + self.output('') + if intro_text: + self.output('%s' % ('\n'.join(intro_text))) else: - log.output('report: %s' % (name)) + self.output('=' * self.line_len) + self.output('RTEMS Tools Project %s' % datetime.datetime.now().ctime()) + if intro_text: + self.output('') + self.output('%s' % ('\n'.join(intro_text))) + self.output('=' * self.line_len) + self.output('Report: %s' % (name)) + self.git_status() def config_start(self, name): first = not self.configs_active self.configs_active = True if self.is_asciidoc(): - log.output('.Config: %s' % name) - log.output('') + self.output('.Config: %s' % name) + self.output('') else: - log.output('-' * self.line_len) - log.output('config: %s' % (name)) + self.output('-' * self.line_len) + self.output('Config: %s' % (name)) def config_end(self, name): if self.is_asciidoc(): - log.output(' ') - log.output("'''") - log.output(' ') + self.output('') + self.output("'''") + self.output('') def buildset_start(self, name): if self.is_asciidoc(): h = '%s' % (name) - log.output('=%s %s' % ('=' * self.bset_nesting, h)) + self.output('=%s %s' % ('=' * self.bset_nesting, h)) else: - log.output('=' * self.line_len) - log.output('build set: %s' % (name)) + self.output('=-' * (self.line_len / 2)) + self.output('Build Set: %s' % (name)) def buildset_end(self, name): self.configs_active = False @@ -135,39 +224,39 @@ class report: package = packages['main'] name = package.name() if self.is_asciidoc(): - log.output('*Package*: _%s_' % name) - log.output(' ') + self.output('*Package*: _%s_' % name) + self.output('') else: - log.output(' package: %s' % (name)) + self.output(' Package: %s' % (name)) sources = package.sources() if self.is_asciidoc(): - log.output('*Sources*;;') + self.output('*Sources*:;;') if len(sources) == 0: - log.output('No sources') + self.output('No sources') else: - log.output(' sources: %d' % (len(sources))) + self.output(' Sources: %d' % (len(sources))) c = 0 for s in sources: c += 1 if self.is_asciidoc(): - log.output('. %s' % (sources[s][0])) + self.output('. %s' % (sources[s][0])) else: - log.output(' %2d: %s' % (c, sources[s][0])) + self.output(' %2d: %s' % (c, sources[s][0])) patches = package.patches() if self.is_asciidoc(): - log.output(' ') - log.output('*Patches*:;;') + self.output('') + self.output('*Patches*:;;') if len(patches) == 0: - log.output('No patches') + self.output('No patches') else: - log.output(' patches: %s' % (len(patches))) + self.output(' Patches: %s' % (len(patches))) c = 0 for p in patches: c += 1 if self.is_asciidoc(): - log.output('. %s' % (patches[p][0])) + self.output('. %s' % (patches[p][0])) else: - log.output(' %2d: %s' % (c, patches[p][0])) + self.output(' %2d: %s' % (c, patches[p][0])) self.config_end(name) def buildset(self, name): @@ -196,33 +285,73 @@ class report: if try_config: self.config(name) - def generate(self): - self.introduction(self.name) - self.buildset(self.name) + def generate(self, name): + if self.is_asciidoc(): + if self.asciidoc is None: + raise error.general('asciidoc not initialised') + import StringIO + infile = StringIO.StringIO(self.out) + outfile = StringIO.StringIO() + self.asciidoc.execute(infile, outfile) + self.out = outfile.getvalue() + infile.close() + outfile.close() + try: + o = open(name, "w") + o.write(self.out) + o.close() + del o + except IOError, err: + raise error.general('writing output file: %s: %s' % (name, err)) + + def make(self, inname, outname, intro_text = None): + self.setup() + self.introduction(inname, intro_text) + self.buildset(inname) + self.generate(outname) def run(args): try: optargs = { '--list-bsets': 'List available build sets', '--list-configs': 'List available configurations', - '--asciidoc': 'Output report as asciidoc' } + '--format': 'Output format (text, asciidoc)', + '--output': 'File name to output the report' } opts, _defaults = defaults.load(args, optargs) log.default = log.log(opts.logfiles()) + if opts.get_arg('--output') and len(opts.params()) > 1: + raise error.general('--output can only be used with a single config') print 'RTEMS Source Builder, Reporter v%s' % (version) if not check.host_setup(opts, _defaults): _notice(opts, 'warning: forcing build with known host setup problems') configs = build.get_configs(opts, _defaults) if not setbuilder.list_bset_cfg_files(opts, configs): + output = opts.get_arg('--output') + if output is not None: + output = output[1] format = 'text' - if opts.get_arg('--asciidoc'): - format = 'asciidoc' - for _file in opts.params(): - r = report(_file, - format = format, - _configs = configs, - _defaults = _defaults, - opts = opts) - r.generate() - del r + ext = '.txt' + format_opt = opts.get_arg('--format') + if format_opt: + if len(format_opt) != 2: + raise error.general('invalid format option: %s' % ('='.join(format_opt))) + if format_opt[1] == 'text': + pass + elif format_opt[1] == 'asciidoc': + format = 'asciidoc' + ext = '.html' + else: + raise error.general('invalid format: %s' % (format_opt[1])) + r = report(format = format, + _configs = configs, + _defaults = _defaults, + opts = opts) + for _config in opts.params(): + if output is None: + outname = path.splitext(_config)[0] + ext + else: + outname = output + r.make(_config, outname) + del r except error.general, gerr: print gerr sys.exit(1) diff --git a/source-builder/sb/windows.py b/source-builder/sb/windows.py index 8ca6d6b..d6e7f6b 100644 --- a/source-builder/sb/windows.py +++ b/source-builder/sb/windows.py @@ -63,6 +63,7 @@ def load(): '__cp': ('exe', 'required', 'cp'), '__cxx': ('exe', 'required', 'g++'), '__flex': ('exe', 'required', 'flex'), + '__git': ('exe', 'required', 'git'), '__grep': ('exe', 'required', 'grep'), '__gzip': ('exe', 'required', 'gzip'), '__id': ('exe', 'required', 'id'),