diff options
author | Chris Johns <chrisj@rtems.org> | 2014-02-15 06:30:06 +1100 |
---|---|---|
committer | Chris Johns <chrisj@rtems.org> | 2014-02-15 06:30:06 +1100 |
commit | 50fdf12244e784bd52732be1e68d966bc9629b24 (patch) | |
tree | 93b6656d397f3ef358b50aeaef6dc4d10a653f8c | |
parent | 8f75c4a380cb0a4330f65966784ccdfeff756e70 (diff) |
rt: Add the rtems-tester.
47 files changed, 8245 insertions, 0 deletions
diff --git a/rtemstoolkit/.gitignore b/rtemstoolkit/.gitignore new file mode 100644 index 0000000..7e4a24c --- /dev/null +++ b/rtemstoolkit/.gitignore @@ -0,0 +1,4 @@ +*~ +*.pyc +*.log +log_* diff --git a/rtemstoolkit/__init__.py b/rtemstoolkit/__init__.py new file mode 100644 index 0000000..33f49f0 --- /dev/null +++ b/rtemstoolkit/__init__.py @@ -0,0 +1,40 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +all = ['check', + 'config', + 'error', + 'execute', + 'git', + 'log', + 'macros', + 'mailer', + 'options', + 'path'] diff --git a/rtemstoolkit/check.py b/rtemstoolkit/check.py new file mode 100644 index 0000000..f4c05b8 --- /dev/null +++ b/rtemstoolkit/check.py @@ -0,0 +1,176 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Check the defaults for a specific host. +# + +import os + +import error +import execute +import log +import options +import path +import version + +def _check_none(_opts, macro, value, constraint): + return True + + +def _check_triplet(_opts, macro, value, constraint): + return True + + +def _check_dir(_opts, macro, value, constraint, silent = False): + if constraint != 'none' and not path.isdir(value): + if constraint == 'required': + if not silent: + log.notice('error: dir: not found: (%s) %s' % (macro, value)) + return False + if not silent and _opts.warn_all(): + log.notice('warning: dir: not found: (%s) %s' % (macro, value)) + return True + + +def _check_exe(_opts, macro, value, constraint, silent = False): + + if len(value) == 0 or constraint == 'none': + return True + + orig_value = value + + if path.isabspath(value): + if path.isfile(value): + return True + if os.name == 'nt': + if path.isfile('%s.exe' % (value)): + return True + value = path.basename(value) + absexe = True + else: + absexe = False + + paths = os.environ['PATH'].split(os.pathsep) + + if _check_paths(value, paths): + if absexe: + if not silent: + log.notice('warning: exe: absolute exe found in path: (%s) %s' % (macro, orig_value)) + return True + + if constraint == 'optional': + if not silent: + log.trace('warning: exe: optional exe not found: (%s) %s' % (macro, orig_value)) + return True + + if not silent: + log.notice('error: exe: not found: (%s) %s' % (macro, orig_value)) + return False + + +def _check_paths(name, paths): + for p in paths: + exe = path.join(p, name) + if path.isfile(exe): + return True + if os.name == 'nt': + if path.isfile('%s.exe' % (exe)): + return True + return False + + +def host_setup(opts): + """ Basic sanity check. All executables and directories must exist.""" + + checks = { 'none': _check_none, + 'triplet': _check_triplet, + 'dir': _check_dir, + 'exe': _check_exe } + + sane = True + + for d in opts.defaults.keys(): + try: + (test, constraint, value) = opts.defaults.get(d) + except: + if opts.defaults.get(d) is None: + raise error.general('invalid default: %s: not found' % (d)) + else: + raise error.general('invalid default: %s [%r]' % (d, opts.defaults.get(d))) + if test != 'none': + value = opts.defaults.expand(value) + if test not in checks: + raise error.general('invalid check test: %s [%r]' % (test, opts.defaults.get(d))) + ok = checks[test](opts, d, value, constraint) + if ok: + tag = ' ' + else: + tag = '*' + log.trace('%c %15s: %r -> "%s"' % (tag, d, opts.defaults.get(d), value)) + if sane and not ok: + sane = False + + return sane + + +def check_exe(label, exe): + return _check_exe(None, label, exe, None, True) + + +def check_dir(label, path): + return _check_dir(None, label, path, 'required', True) + + +def run(): + import sys + try: + _opts = options.load(args = sys.argv) + log.notice('RTEMS Source Builder - Check, v%s' % (version.str())) + if host_setup(_opts): + print 'Environment is ok' + else: + print 'Environment is not correctly set up' + except error.general, gerr: + print gerr + sys.exit(1) + except error.internal, ierr: + print ierr + sys.exit(1) + except error.exit, eerr: + pass + except KeyboardInterrupt: + log.notice('abort: user terminated') + sys.exit(1) + sys.exit(0) + + +if __name__ == '__main__': + run() diff --git a/rtemstoolkit/config.py b/rtemstoolkit/config.py new file mode 100644 index 0000000..306e3df --- /dev/null +++ b/rtemstoolkit/config.py @@ -0,0 +1,857 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# This code is based on a tool I wrote to parse RPM spec files in the RTEMS +# project. This is now a configuration file format that has moved away from the +# spec file format to support the specific needs of cross-compiling GCC. This +# module parses a configuration file into Python data types that can be used by +# other software modules. +# + +import copy +import os +import re +import sys + +try: + import error + import execute + import log + import options + import path +except KeyboardInterrupt: + print 'user terminated' + sys.exit(1) +except: + print 'error: unknown application load error' + sys.exit(1) + +def _check_bool(value): + if value.isdigit(): + if int(value) == 0: + istrue = False + else: + istrue = True + else: + istrue = None + return istrue + +class file(object): + """Parse a config file.""" + + def __init__(self, name, opts, macros = None, directives = None, ignores = None): + self.opts = opts + if macros is None: + self.macros = opts.defaults + else: + self.macros = macros + self.init_name = name + self.directives = ['%include'] + if directives: + self.directives += directives + self.ignores = ignores + log.trace('config: %s' % (name)) + self.disable_macro_reassign = False + self.configpath = [] + self.wss = re.compile(r'\s+') + self.tags = re.compile(r':+') + self.sf = re.compile(r'%\([^\)]+\)') + for arg in self.opts.args: + if arg.startswith('--with-') or arg.startswith('--without-'): + label = arg[2:].lower().replace('-', '_') + self.macros.define(label) + self._includes = [] + self.load_depth = 0 + + def __del__(self): + pass + + def __str__(self): + + def _dict(dd): + s = '' + ddl = dd.keys() + ddl.sort() + for d in ddl: + s += ' ' + d + ': ' + dd[d] + '\n' + return s + + s = 'config: %s' % ('.'.join(self.configpath)) + \ + '\n' + str(self.opts) + \ + '\nlines parsed: %d' % (self.lc) + \ + '\nname: ' + self.name + \ + '\nmacros:\n' + str(self.macros) + return s + + def _name_line_msg(self, msg): + return '%s:%d: %s' % (path.basename(self.init_name), self.lc, msg) + + def _output(self, text): + if not self.opts.quiet(): + log.output(text) + + def _error(self, msg): + err = 'error: %s' % (self._name_line_msg(msg)) + log.stderr(err) + log.output(err) + self.in_error = True + if not self.opts.dry_run(): + log.stderr('warning: switched to dry run due to errors') + self.opts.set_dry_run() + + def _label(self, name): + if name.startswith('%{') and name[-1] is '}': + return name + return '%{' + name.lower() + '}' + + def _macro_split(self, s): + '''Split the string (s) up by macros. Only split on the + outter level. Nested levels will need to split with futher calls.''' + trace_me = False + if trace_me: + print '------------------------------------------------------' + macros = [] + nesting = [] + has_braces = False + c = 0 + while c < len(s): + if trace_me: + print 'ms:', c, '"' + s[c:] + '"', has_braces, len(nesting), nesting + # + # We need to watch for shell type variables or the form '${var}' because + # they can upset the brace matching. + # + if s[c] == '%' or s[c] == '$': + start = s[c] + c += 1 + if c == len(s): + continue + # + # Do we have '%%' or '%(' or '$%' or '$(' or not '${' ? + # + if s[c] == '%' or s[c] == '(' or (start == '$' and s[c] != '{'): + continue + elif not s[c].isspace(): + # + # If this is a shell macro and we are at the outter + # level or is '$var' forget it and move on. + # + if start == '$' and (s[c] != '{' or len(nesting) == 0): + continue + if s[c] == '{': + this_has_braces = True + else: + this_has_braces = False + nesting.append((c - 1, has_braces)) + has_braces = this_has_braces + elif len(nesting) > 0: + if s[c] == '}' or (s[c].isspace() and not has_braces): + # + # Can have '%{?test: something %more}' where the + # nested %more ends with the '}' which also ends + # the outter macro. + # + if not has_braces: + if s[c] == '}': + macro_start, has_braces = nesting[len(nesting) - 1] + nesting = nesting[:-1] + if len(nesting) == 0: + macros.append(s[macro_start:c].strip()) + if len(nesting) > 0: + macro_start, has_braces = nesting[len(nesting) - 1] + nesting = nesting[:-1] + if len(nesting) == 0: + macros.append(s[macro_start:c + 1].strip()) + c += 1 + if trace_me: + print 'ms:', macros + if trace_me: + print '-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=' + return macros + + def _shell(self, line): + sl = self.sf.findall(line) + if len(sl): + e = execute.capture_execution() + for s in sl: + if options.host_windows: + cmd = '%s -c "%s"' % (self.macros.expand('%{__sh}'), s[2:-1]) + else: + cmd = s[2:-1] + exit_code, proc, output = e.shell(cmd) + if exit_code == 0: + line = line.replace(s, output) + else: + raise error.general('shell macro failed: %s:%d: %s' % (s, exit_code, output)) + return line + + def _expand(self, s): + expand_count = 0 + expanded = True + while expanded: + expand_count += 1 + if expand_count > 500: + raise error.general('macro expand looping: %s' % (s)) + expanded = False + ms = self._macro_split(s) + for m in ms: + mn = m + # + # A macro can be '%{macro}' or '%macro'. Turn the later into + # the former. + # + show_warning = True + if mn[1] != '{': + if self.ignores is not None: + for r in self.ignores: + if r.match(mn) is not None: + mn = None + break + else: + mn = self._label(mn[1:]) + show_warning = False + else: + mn = self._label(mn[1:]) + show_warning = False + elif m.startswith('%{expand'): + colon = m.find(':') + if colon < 8: + log.warning('malformed expand macro, no colon found') + else: + e = self._expand(m[colon + 1:-1].strip()) + s = s.replace(m, e) + expanded = True + mn = None + elif m.startswith('%{with '): + # + # Change the ' ' to '_' because the macros have no spaces. + # + n = self._label('with_' + m[7:-1].strip()) + if n in self.macros: + s = s.replace(m, '1') + else: + s = s.replace(m, '0') + expanded = True + mn = None + elif m.startswith('%{echo'): + if not m.endswith('}'): + log.warning("malformed conditional macro '%s'" % (m)) + mn = None + else: + e = self._expand(m[6:-1].strip()) + log.output('%s' % (self._name_line_msg(e))) + s = '' + expanded = True + mn = None + elif m.startswith('%{defined'): + n = self._label(m[9:-1].strip()) + if n in self.macros: + s = s.replace(m, '1') + else: + s = s.replace(m, '0') + expanded = True + mn = None + elif m.startswith('%{?') or m.startswith('%{!?'): + if m[2] == '!': + start = 4 + else: + start = 3 + colon = m[start:].find(':') + if colon < 0: + if not m.endswith('}'): + log.warning("malformed conditional macro '%s'" % (m)) + mn = None + else: + mn = self._label(m[start:-1]) + else: + mn = self._label(m[start:start + colon]) + if mn: + if m.startswith('%{?'): + istrue = False + if mn in self.macros: + # If defined and 0 then it is false. + istrue = _check_bool(self.macros[mn]) + if istrue is None: + istrue = True + if colon >= 0 and istrue: + s = s.replace(m, m[start + colon + 1:-1]) + expanded = True + mn = None + elif not istrue: + mn = '%{nil}' + else: + isfalse = True + if mn in self.macros: + istrue = _check_bool(self.macros[mn]) + if istrue is None or istrue == True: + isfalse = False + if colon >= 0 and isfalse: + s = s.replace(m, m[start + colon + 1:-1]) + expanded = True + mn = None + else: + mn = '%{nil}' + if mn: + if mn.lower() in self.macros: + s = s.replace(m, self.macros[mn.lower()]) + expanded = True + elif show_warning: + self._error("macro '%s' not found" % (mn)) + return self._shell(s) + + def _disable(self, config, ls): + if len(ls) != 2: + log.warning('invalid disable statement') + else: + if ls[1] == 'select': + self.macros.lock_read_map() + log.trace('config: %s: _disable_select: %s' % (self.init_name, ls[1])) + else: + log.warning('invalid disable statement: %s' % (ls[1])) + + def _select(self, config, ls): + if len(ls) != 2: + log.warning('invalid select statement') + else: + r = self.macros.set_read_map(ls[1]) + log.trace('config: %s: _select: %s %s %r' % \ + (self.init_name, r, ls[1], self.macros.maps())) + + def _define(self, config, ls): + if len(ls) <= 1: + log.warning('invalid macro definition') + else: + d = self._label(ls[1]) + if self.disable_macro_reassign: + if (d not in self.macros) or \ + (d in self.macros and len(self.macros[d]) == 0): + if len(ls) == 2: + self.macros[d] = '1' + else: + self.macros[d] = ' '.join([f.strip() for f in ls[2:]]) + else: + log.warning("macro '%s' already defined" % (d)) + else: + if len(ls) == 2: + self.macros[d] = '1' + else: + self.macros[d] = ' '.join([f.strip() for f in ls[2:]]) + + def _undefine(self, config, ls): + if len(ls) <= 1: + log.warning('invalid macro definition') + else: + mn = self._label(ls[1]) + if mn in self.macros: + del self.macros[mn] + else: + log.warning("macro '%s' not defined" % (mn)) + + def _ifs(self, config, ls, label, iftrue, isvalid, dir, info): + in_iftrue = True + data = [] + while True: + if isvalid and \ + ((iftrue and in_iftrue) or (not iftrue and not in_iftrue)): + this_isvalid = True + else: + this_isvalid = False + r = self._parse(config, dir, info, roc = True, isvalid = this_isvalid) + if r[0] == 'control': + if r[1] == '%end': + self._error(label + ' without %endif') + raise error.general('terminating build') + if r[1] == '%endif': + log.trace('config: %s: _ifs: %s %s' % (self.init_name, r[1], this_isvalid)) + return data + if r[1] == '%else': + in_iftrue = False + elif r[0] == 'directive': + if this_isvalid: + if r[1] == '%include': + self.load(r[2][0]) + continue + dir, info, data = self._process_directive(r, dir, info, data) + elif r[0] == 'data': + if this_isvalid: + dir, info, data = self._process_data(r, dir, info, data) + else: + dir, info, data = self._process_block(r, dir, info, data) + + # @note is a directive extend missing + + def _if(self, config, ls, isvalid, dir, info, invert = False): + + def add(x, y): + return x + ' ' + str(y) + + istrue = False + if isvalid: + if len(ls) == 2: + s = ls[1] + else: + s = (ls[1] + ' ' + ls[2]) + ifls = s.split() + if len(ifls) == 1: + # + # Check if '%if %{x} == %{nil}' has both parts as nothing + # which means '%if ==' is always True and '%if !=' is always false. + # + if ifls[0] == '==': + istrue = True + elif ifls[0] == '!=': + istrue = False + else: + istrue = _check_bool(ifls[0]) + if istrue == None: + self._error('invalid if bool value: ' + reduce(add, ls, '')) + istrue = False + elif len(ifls) == 2: + if ifls[0] == '!': + istrue = _check_bool(ifls[1]) + if istrue == None: + self._error('invalid if bool value: ' + reduce(add, ls, '')) + istrue = False + else: + istrue = not istrue + else: + # + # Check is something is being checked against empty, + # ie '%if %{x} == %{nil}' + # The logic is 'something == nothing' is False and + # 'something != nothing' is True. + # + if ifls[1] == '==': + istrue = False + elif ifls[1] == '!=': + istrue = True + else: + self._error('invalid if bool operator: ' + reduce(add, ls, '')) + elif len(ifls) == 3: + if ifls[1] == '==': + if ifls[0] == ifls[2]: + istrue = True + else: + istrue = False + elif ifls[1] == '!=' or ifls[1] == '=!': + if ifls[0] != ifls[2]: + istrue = True + else: + istrue = False + elif ifls[1] == '>': + if ifls[0] > ifls[2]: + istrue = True + else: + istrue = False + elif ifls[1] == '>=' or ifls[1] == '=>': + if ifls[0] >= ifls[2]: + istrue = True + else: + istrue = False + elif ifls[1] == '<=' or ifls[1] == '=<': + if ifls[0] <= ifls[2]: + istrue = True + else: + istrue = False + elif ifls[1] == '<': + if ifls[0] < ifls[2]: + istrue = True + else: + istrue = False + else: + self._error('invalid %if operator: ' + reduce(add, ls, '')) + else: + self._error('malformed if: ' + reduce(add, ls, '')) + if invert: + istrue = not istrue + log.trace('config: %s: _if: %s %s' % (self.init_name, ifls, str(istrue))) + return self._ifs(config, ls, '%if', istrue, isvalid, dir, info) + + def _ifos(self, config, ls, isvalid, dir, info): + isos = False + if isvalid: + os = self.define('_os') + for l in ls: + if l in os: + isos = True + break + return self._ifs(config, ls, '%ifos', isos, isvalid, dir, info) + + def _ifarch(self, config, positive, ls, isvalid, dir, info): + isarch = False + if isvalid: + arch = self.define('_arch') + for l in ls: + if l in arch: + isarch = True + break + if not positive: + isarch = not isarch + return self._ifs(config, ls, '%ifarch', isarch, isvalid, dir, info) + + def _parse(self, config, dir, info, roc = False, isvalid = True): + # roc = return on control + + def _clean(line): + line = line[0:-1] + b = line.find('#') + if b >= 0: + line = line[1:b] + return line.strip() + + # + # Need to add code to count matching '{' and '}' and if they + # do not match get the next line and add to the string until + # they match. This closes an opening '{' that is on another + # line. + # + for l in config: + self.lc += 1 + l = _clean(l) + if len(l) == 0: + continue + log.trace('config: %s: %03d: %s %s' % \ + (self.init_name, self.lc, str(isvalid), l)) + lo = l + if isvalid: + l = self._expand(l) + if len(l) == 0: + continue + if l[0] == '%': + ls = self.wss.split(l, 2) + los = self.wss.split(lo, 2) + if ls[0] == '%disable': + if isvalid: + self._disable(config, ls) + elif ls[0] == '%select': + if isvalid: + self._select(config, ls) + elif ls[0] == '%error': + if isvalid: + return ('data', ['%%error %s' % (self._name_line_msg(l[7:]))]) + elif ls[0] == '%warning': + if isvalid: + return ('data', ['%%warning %s' % (self._name_line_msg(l[9:]))]) + elif ls[0] == '%define' or ls[0] == '%global': + if isvalid: + self._define(config, ls) + elif ls[0] == '%undefine': + if isvalid: + self._undefine(config, ls) + elif ls[0] == '%if': + d = self._if(config, ls, isvalid, dir, info) + if len(d): + log.trace('config: %s: %%if: %s' % (self.init_name, d)) + return ('data', d) + elif ls[0] == '%ifn': + d = self._if(config, ls, isvalid, dir, info, True) + if len(d): + log.trace('config: %s: %%ifn: %s' % (self.init_name, d)) + return ('data', d) + elif ls[0] == '%ifos': + d = self._ifos(config, ls, isvalid, dir, info) + if len(d): + return ('data', d) + elif ls[0] == '%ifarch': + d = self._ifarch(config, True, ls, isvalid, dir, info) + if len(d): + return ('data', d) + elif ls[0] == '%ifnarch': + d = self._ifarch(config, False, ls, isvalid, dir, info) + if len(d): + return ('data', d) + elif ls[0] == '%endif': + if roc: + return ('control', '%endif', '%endif') + log.warning("unexpected '" + ls[0] + "'") + elif ls[0] == '%else': + if roc: + return ('control', '%else', '%else') + log.warning("unexpected '" + ls[0] + "'") + elif ls[0].startswith('%defattr'): + return ('data', [l]) + elif ls[0] == '%bcond_with': + if isvalid: + # + # Check if already defined. Would be by the command line or + # even a host specific default. + # + if self._label('with_' + ls[1]) not in self.macros: + self._define(config, (ls[0], 'without_' + ls[1])) + elif ls[0] == '%bcond_without': + if isvalid: + if self._label('without_' + ls[1]) not in self.macros: + self._define(config, (ls[0], 'with_' + ls[1])) + else: + pt = self._parse_token(lo, los, l, ls) + if pt is not None: + return pt + if self.ignores is not None: + for r in self.ignores: + if r.match(ls[0]) is not None: + return ('data', [l]) + if isvalid: + for d in self.directives: + if ls[0].strip() == d: + return ('directive', ls[0].strip(), ls[1:]) + log.warning("unknown directive: '" + ls[0] + "'") + return ('data', [lo]) + else: + return ('data', [lo]) + return ('control', '%end', '%end') + + def _parse_token(self, line, line_split, line_expanded, line_split_expanded): + return None + + def _process_directive(self, results, directive, info, data): + new_data = [] + if results[1] == '%description': + new_data = [' '.join(results[2])] + else: + directive, into, data = self._directive_filter(results, directive, info, data) + if directive and directive != results[1]: + self._directive_extend(directive, data) + directive = results[1] + data = new_data + return (directive, info, data) + + def _process_data(self, results, directive, info, data): + new_data = [] + for l in results[1]: + if l.startswith('%error'): + l = self._expand(l) + raise error.general('config error: %s' % (l[7:])) + elif l.startswith('%warning'): + l = self._expand(l) + log.stderr('warning: %s' % (l[9:])) + log.warning(l[9:]) + if not directive: + l = self._expand(l) + ls = self.tags.split(l, 1) + log.trace('config: %s: _tag: %s %s' % (self.init_name, l, ls)) + if len(ls) > 1: + info = ls[0].lower() + if info[-1] == ':': + info = info[:-1] + info_data = ls[1].strip() + else: + info_data = ls[0].strip() + if info is not None: + self._info_append(info, info_data) + else: + log.warning("invalid format: '%s'" % (info_data[:-1])) + else: + log.trace('config: %s: _data: %s %s' % (self.init_name, l, new_data)) + new_data.append(l) + return (directive, info, data + new_data) + + def _process_block(self, results, directive, info, data): + raise error.internal('known block type: %s' % (results[0])) + + def _directive_extend(self, dir, data): + pass + + def _directive_filter(self, results, directive, info, data): + return directive, into, data + + def _info_append(self, info, data): + pass + + def load(self, name): + + def common_end(left, right): + end = '' + while len(left) and len(right): + if left[-1] != right[-1]: + return end + end = left[-1] + end + left = left[:-1] + right = right[:-1] + return end + + if self.load_depth == 0: + self.in_error = False + self.lc = 0 + self.name = name + self.conditionals = {} + + self.load_depth += 1 + + save_name = self.name + save_lc = self.lc + + self.name = name + self.lc = 0 + + # + # Locate the config file. Expand any macros then add the + # extension. Check if the file exists, therefore directly + # referenced. If not see if the file contains ':' or the path + # separator. If it does split the path else use the standard config dir + # path in the defaults. + # + exname = self.expand(name) + + # + # Macro could add an extension. + # + if exname.endswith('.cfg'): + configname = exname + else: + configname = '%s.cfg' % (exname) + name = '%s.cfg' % (name) + + if ':' in configname: + cfgname = path.basename(configname) + else: + cfgname = common_end(configname, name) + + if not path.exists(configname): + if ':' in configname: + configdirs = path.dirname(configname).split(':') + else: + configdirs = self.define('_configdir').split(':') + for cp in configdirs: + configname = path.join(path.abspath(cp), cfgname) + if path.exists(configname): + break + configname = None + if configname is None: + raise error.general('no config file found: %s' % (cfgname)) + + try: + log.trace('config: %s: _open: %s' % (self.init_name, path.host(configname))) + config = open(path.host(configname), 'r') + except IOError, err: + raise error.general('error opening config file: %s' % (path.host(configname))) + self.configpath += [configname] + + self._includes += [configname] + + try: + dir = None + info = None + data = [] + while True: + r = self._parse(config, dir, info) + if r[0] == 'control': + if r[1] == '%end': + break + log.warning("unexpected '%s'" % (r[1])) + elif r[0] == 'directive': + if r[1] == '%include': + self.load(r[2][0]) + continue + dir, info, data = self._process_directive(r, dir, info, data) + elif r[0] == 'data': + dir, info, data = self._process_data(r, dir, info, data) + else: + self._error("%d: invalid parse state: '%s" % (self.lc, r[0])) + if dir is not None: + self._directive_extend(dir, data) + except: + config.close() + raise + + config.close() + + self.name = save_name + self.lc = save_lc + + self.load_depth -= 1 + + def defined(self, name): + return self.macros.has_key(name) + + def define(self, name): + if name in self.macros: + d = self.macros[name] + else: + n = self._label(name) + if n in self.macros: + d = self.macros[n] + else: + raise error.general('%d: macro "%s" not found' % (self.lc, name)) + return self._expand(d) + + def set_define(self, name, value): + self.macros[name] = value + + def expand(self, line): + if type(line) == list: + el = [] + for l in line: + el += [self._expand(l)] + return el + return self._expand(line) + + def macro(self, name): + if name in self.macros: + return self.macros[name] + raise error.general('macro "%s" not found' % (name)) + + def directive(self, name): + pass + + def abspath(self, rpath): + return path.abspath(self.define(rpath)) + + def includes(self): + return self._includes + + def file_name(self): + return self.init_name + +def run(): + import sys + try: + # + # Run where defaults.mc is located + # + opts = options.load(sys.argv, defaults = 'defaults.mc') + log.trace('config: count %d' % (len(opts.config_files()))) + for config_file in opts.config_files(): + s = file(config_file, opts) + print s + del s + except error.general, gerr: + print gerr + sys.exit(1) + except error.internal, ierr: + print ierr + sys.exit(1) + except KeyboardInterrupt: + log.notice('abort: user terminated') + sys.exit(1) + sys.exit(0) + +if __name__ == "__main__": + run() diff --git a/rtemstoolkit/darwin.py b/rtemstoolkit/darwin.py new file mode 100644 index 0000000..e858ace --- /dev/null +++ b/rtemstoolkit/darwin.py @@ -0,0 +1,79 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# This code is based on what ever doco about spec files I could find and +# RTEMS project's spec files. +# + +import os + +import execute + +def load(): + uname = os.uname() + sysctl = '/usr/sbin/sysctl ' + e = execute.capture_execution() + exit_code, proc, output = e.shell(sysctl + 'hw.ncpu') + if exit_code == 0: + ncpus = output.split(' ')[1].strip() + else: + ncpus = '1' + defines = { + '_ncpus': ('none', 'none', ncpus), + '_os': ('none', 'none', 'darwin'), + '_host': ('triplet', 'required', uname[4] + '-apple-darwin' + uname[2]), + '_host_vendor': ('none', 'none', 'apple'), + '_host_os': ('none', 'none', 'darwin'), + '_host_cpu': ('none', 'none', uname[4]), + '_host_alias': ('none', 'none', '%{nil}'), + '_host_arch': ('none', 'none', uname[4]), + '_usr': ('dir', 'optional', '/usr/local'), + '_var': ('dir', 'optional', '/usr/local/var'), + '_prefix': ('dir', 'optional', '%{_usr}'), + '__ldconfig': ('exe', 'none', ''), + '__cvs': ('exe', 'required', 'cvs'), + '__xz': ('exe', 'required', '%{_usr}/bin/xz'), + 'with_zlib': ('none', 'none', '--with-zlib=no'), + '_forced_static': ('none', 'none', '') + } + + defines['_build'] = defines['_host'] + defines['_build_vendor'] = defines['_host_vendor'] + defines['_build_os'] = defines['_host_os'] + defines['_build_cpu'] = defines['_host_cpu'] + defines['_build_alias'] = defines['_host_alias'] + defines['_build_arch'] = defines['_host_arch'] + + return defines + +if __name__ == '__main__': + import pprint + pprint.pprint(load()) diff --git a/rtemstoolkit/error.py b/rtemstoolkit/error.py new file mode 100644 index 0000000..89ea181 --- /dev/null +++ b/rtemstoolkit/error.py @@ -0,0 +1,65 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Various errors we can raise. +# + +class error(Exception): + """Base class for Builder exceptions.""" + def set_output(self, msg): + self.msg = msg + def __str__(self): + return self.msg + +class general(error): + """Raise for a general error.""" + def __init__(self, what): + self.set_output('error: ' + what) + +class internal(error): + """Raise for an internal error.""" + def __init__(self, what): + self.set_output('internal error: ' + what) + +class exit(error): + """Raise for to exit.""" + def __init__(self): + pass + +if __name__ == '__main__': + try: + raise general('a general error') + except general, gerr: + print 'caught:', gerr + try: + raise internal('an internal error') + except internal, ierr: + print 'caught:', ierr diff --git a/rtemstoolkit/execute.py b/rtemstoolkit/execute.py new file mode 100755 index 0000000..1fc16d9 --- /dev/null +++ b/rtemstoolkit/execute.py @@ -0,0 +1,517 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Execute commands or scripts. +# +# Note, the subprocess module is only in Python 2.4 or higher. +# + +import os +import re +import sys +import subprocess +import threading +import time + +import error +import log + +# Trace exceptions +trace_threads = False + +# Redefine the PIPE from subprocess +PIPE = subprocess.PIPE + +# Regular expression to find quotes. +qstr = re.compile('[rR]?\'([^\\n\'\\\\]|\\\\.)*\'|[rR]?"([^\\n"\\\\]|\\\\.)*"') + +def check_type(command): + """Checks the type of command we have. The types are spawn and + shell.""" + if command in ['spawn', 'shell']: + return True + return False + +def arg_list(args): + """Turn a string of arguments into a list suitable for + spawning a command. If the args are already a list return + it.""" + if type(args) is list: + return args + argstr = args + args = [] + while len(argstr): + qs = qstr.search(argstr) + if not qs: + args.extend(argstr.split()) + argstr= '' + else: + # We have a quoted string. Get the string before + # the quoted string and splt on white space then + # add the quoted string as an option then remove + # the first + quoted string and try again + front = argstr[:qs.start()] + args.extend(front.split()) + args.append(argstr[qs.start() + 1:qs.end() - 1]) + argstr = argstr[qs.end():] + return args + +def arg_subst(command, substs): + """Substitute the %[0-9] in the command with the subst values.""" + args = arg_list(command) + if substs: + for a in range(0, len(args)): + for r in range(0, len(substs)): + args[a] = re.compile(('%%%d' % (r))).sub(substs[r], args[a]) + return args + +def arg_subst_str(command, subst): + cmd = arg_subst(command, subst) + def add(x, y): return x + ' ' + str(y) + return reduce(add, cmd, '') + +class execute(object): + """Execute commands or scripts. The 'output' is a funtion that handles the + output from the process. The 'input' is a function that blocks and returns + data to be written to stdin""" + def __init__(self, output = None, input = None, cleanup = None, + error_prefix = '', verbose = False): + self.lock = threading.Lock() + self.output = output + self.input = input + self.cleanup = cleanup + self.error_prefix = error_prefix + self.verbose = verbose + self.shell_exe = None + self.shell_commands = False + self.path = None + self.environment = None + self.outputting = False + self.timing_out = False + + def _capture(self, command, proc, timeout = None): + """Create 3 threads to read stdout and stderr and send to the output handler + and call an input handler is provided. Based on the 'communicate' code + in the subprocess module.""" + def _writethread(exe, fh, input): + """Call the input handler and write it to the stdin. The input handler should + block and return None or False if this thread is to exit and True if this + is a timeout check.""" + if trace_threads: + print 'executte:_writethread: start' + try: + while True: + lines = input() + if type(lines) == str: + try: + fh.write(lines) + except: + break + if lines == None or \ + lines == False or \ + (lines == True and fh.closed): + break + except: + if trace_threads: + print 'executte:_writethread: exception' + pass + try: + fh.close() + except: + pass + if trace_threads: + print 'executte:_writethread: finished' + + def _readthread(exe, fh, out, prefix = ''): + """Read from a file handle and write to the output handler + until the file closes.""" + def _output_line(line, exe, prefix, out, count): + #print 'LINE:%d: %s' % (count, line) + exe.lock.acquire() + #exe.outputting = True + exe.lock.release() + if out: + out(prefix + line) + else: + log.output(prefix + line) + if count > 10: + log.flush() + + if trace_threads: + print 'executte:_readthread: start' + count = 0 + line = '' + try: + while True: + data = fh.read(1) + if len(data) == 0: + break + #print '))))) %02x "%s"' % (ord(data), data) + for c in data: + line += c + if c == '\n': + count += 1 + _output_line(line, exe, prefix, out, count) + if count > 10: + count = 0 + line = '' + except: + raise + if trace_threads: + print 'executte:_readthread: exception' + pass + try: + fh.close() + except: + pass + if len(line): + _output_line(line, exe, prefix, out, 100) + if trace_threads: + print 'executte:_readthread: finished' + + def _timerthread(exe, interval, function): + """Timer thread is used to timeout a process if no output is + produced for the timeout interval.""" + count = interval + while exe.timing_out: + time.sleep(1) + if count > 0: + count -= 1 + exe.lock.acquire() + if exe.outputting: + count = interval + exe.outputting = False + exe.lock.release() + if count == 0: + try: + proc.kill() + except: + pass + else: + function() + break + + name = os.path.basename(command[0]) + + stdin_thread = None + stdout_thread = None + stderr_thread = None + timeout_thread = None + + if proc.stdout: + stdout_thread = threading.Thread(target = _readthread, + name = '_stdout[%s]' % (name), + args = (self, + proc.stdout, + self.output, + '')) + stdout_thread.daemon = True + stdout_thread.start() + if proc.stderr: + stderr_thread = threading.Thread(target = _readthread, + name = '_stderr[%s]' % (name), + args = (self, + proc.stderr, + self.output, + self.error_prefix)) + stderr_thread.daemon = True + stderr_thread.start() + if self.input and proc.stdin: + stdin_thread = threading.Thread(target = _writethread, + name = '_stdin[%s]' % (name), + args = (self, + proc.stdin, + self.input)) + stdin_thread.daemon = True + stdin_thread.start() + if timeout: + self.timing_out = True + timeout_thread = threading.Thread(target = _timerthread, + name = '_timeout[%s]' % (name), + args = (self, + timeout[0], + timeout[1])) + timeout_thread.daemon = True + timeout_thread.start() + try: + exitcode = proc.wait() + except: + print 'killing' + proc.kill() + raise + finally: + if self.cleanup: + self.cleanup(proc) + if timeout_thread: + self.timing_out = False + timeout_thread.join(10) + if stdin_thread: + stdin_thread.join(2) + if stdout_thread: + stdout_thread.join(2) + if stderr_thread: + stderr_thread.join(2) + return exitcode + + def open(self, command, capture = True, shell = False, + cwd = None, env = None, + stdin = None, stdout = None, stderr = None, + timeout = None): + """Open a command with arguments. Provide the arguments as a list or + a string.""" + if self.verbose: + s = command + if type(command) is list: + def add(x, y): return x + ' ' + str(y) + s = reduce(add, command, '')[1:] + what = 'spawn' + if shell: + what = 'shell' + log.output(what + ': ' + s) + if shell and self.shell_exe: + command = arg_list(command) + command[:0] = self.shell_exe + if not stdin and self.input: + stdin = subprocess.PIPE + if not stdout: + stdout = subprocess.PIPE + if not stderr: + stderr = subprocess.PIPE + proc = None + if cwd is None: + cwd = self.path + if env is None: + env = self.environment + try: + # Work around a problem on Windows with commands that + # have a '.' and no extension. Windows needs the full + # command name. + if sys.platform == "win32" and type(command) is list: + if command[0].find('.') >= 0: + r, e = os.path.splitext(command[0]) + if e not in ['.exe', '.com', '.bat']: + command[0] = command[0] + '.exe' + log.trace('exe: %s' % (command)) + proc = subprocess.Popen(command, shell = shell, + cwd = cwd, env = env, + stdin = stdin, stdout = stdout, + stderr = stderr) + if not capture: + return (0, proc) + if self.output is None: + raise error.general('capture needs an output handler') + exit_code = self._capture(command, proc, timeout) + if self.verbose: + log.output('exit: ' + str(exit_code)) + except OSError, ose: + exit_code = ose.errno + if self.verbose: + log.output('exit: ' + str(ose)) + return (exit_code, proc) + + def spawn(self, command, capture = True, cwd = None, env = None, + stdin = None, stdout = None, stderr = None, + timeout = None): + """Spawn a command with arguments. Provide the arguments as a list or + a string.""" + return self.open(command, capture, False, cwd, env, + stdin, stdout, stderr, timeout) + + def shell(self, command, capture = True, cwd = None, env = None, + stdin = None, stdout = None, stderr = None, + timeout = None): + """Execute a command within a shell context. The command can contain + argumments. The shell is specific to the operating system. For example + it is cmd.exe on Windows XP.""" + return self.open(command, capture, True, cwd, env, + stdin, stdout, stderr, timeout) + + def command(self, command, args = None, capture = True, shell = False, + cwd = None, env = None, + stdin = None, stdout = None, stderr = None, + timeout = None): + """Run the command with the args. The args can be a list + or a string.""" + if args and not type(args) is list: + args = arg_list(args) + cmd = [command] + if args: + cmd.extend(args) + return self.open(cmd, capture = capture, shell = shell, + cwd = cwd, env = env, + stdin = stdin, stdout = stdout, stderr = stderr, + timeout = timeout) + + def command_subst(self, command, substs, capture = True, shell = False, + cwd = None, env = None, + stdin = None, stdout = None, stderr = None, + timeout = None): + """Run the command from the config data with the + option format string subsituted with the subst variables.""" + args = arg_subst(command, substs) + return self.command(args[0], args[1:], capture = capture, + shell = shell or self.shell_commands, + cwd = cwd, env = env, + stdin = stdin, stdout = stdout, stderr = stderr, + itmeout = timeout) + + def set_shell(self, execute): + """Set the shell to execute when issuing a shell command.""" + args = arg_list(execute) + if len(args) == 0 or not os.path.isfile(args[0]): + raise error.general('could find shell: ' + execute) + self.shell_exe = args + + def command_use_shell(self): + """Force all commands to use a shell. This can be used with set_shell + to allow Unix commands be executed on Windows with a Unix shell such + as Cygwin or MSYS. This may cause piping to fail.""" + self.shell_commands = True + + def set_output(self, output): + """Set the output handler. The stdout of the last process in a pipe + line is passed to this handler.""" + old_output = self.output + self.output = output + return old_output + + def set_path(self, path): + """Set the path changed to before the child process is created.""" + old_path = self.path + self.path = path + return old_path + + def set_environ(self, environment): + """Set the environment passed to the child process when created.""" + old_environment = self.environment + self.environment = environment + return old_environment + +class capture_execution(execute): + """Capture all output as a string and return it.""" + + class _output_snapper: + def __init__(self, log = None, dump = False): + self.output = '' + self.log = log + self.dump = dump + + def handler(self, text): + if not self.dump: + if self.log is not None: + self.log.output(text) + else: + self.output += text + + def get_and_clear(self): + text = self.output + self.output = '' + return text.strip() + + def __init__(self, log = None, dump = False, error_prefix = '', verbose = False): + self.snapper = capture_execution._output_snapper(log = log, dump = dump) + execute.__init__(self, output = self.snapper.handler, + error_prefix = error_prefix, + verbose = verbose) + + def open(self, command, capture = True, shell = False, cwd = None, env = None, + stdin = None, stdout = None, stderr = None, timeout = None): + if not capture: + raise error.general('output capture must be true; leave as default') + #self.snapper.get_and_clear() + exit_code, proc = execute.open(self, command, capture = True, shell = shell, + cwd = cwd, env = env, + stdin = stdin, stdout = stdout, stderr = stderr, + timeout = timeout) + return (exit_code, proc, self.snapper.get_and_clear()) + + def set_output(self, output): + raise error.general('output capture cannot be overrided') + +if __name__ == "__main__": + def run_tests(e, commands, use_shell): + for c in commands['shell']: + e.shell(c) + for c in commands['spawn']: + e.spawn(c) + for c in commands['cmd']: + if type(c) is str: + e.command(c, shell = use_shell) + else: + e.command(c[0], c[1], shell = use_shell) + for c in commands['csubsts']: + e.command_subst(c[0], c[1], shell = use_shell) + ec, proc = e.command(commands['pipe'][0], commands['pipe'][1], + capture = False, stdin = subprocess.PIPE) + if ec == 0: + print 'piping input into ' + commands['pipe'][0] + ': ' + \ + commands['pipe'][2] + proc.stdin.write(commands['pipe'][2]) + proc.stdin.close() + e.capture(proc) + del proc + + cmd_shell_test = 'if "%OS%" == "Windows_NT" (echo It is WinNT) else echo Is is not WinNT' + sh_shell_test = 'x="me"; if [ $x = "me" ]; then echo "It was me"; else "It was him"; fi' + + commands = {} + commands['windows'] = {} + commands['unix'] = {} + commands['windows']['shell'] = ['cd', 'dir /w', '.\\xyz', cmd_shell_test] + commands['windows']['spawn'] = ['hostname', 'hostnameZZ', ['netstat', '/e']] + commands['windows']['cmd'] = [('ipconfig'), ('nslookup', 'www.python.org')] + commands['windows']['csubsts'] = [('netstat %0', ['-a']), + ('netstat %0 %1', ['-a', '-n'])] + commands['windows']['pipe'] = ('ftp', None, 'help\nquit') + commands['unix']['shell'] = ['pwd', 'ls -las', './xyz', sh_shell_test] + commands['unix']['spawn'] = ['ls', 'execute.pyc', ['ls', '-i']] + commands['unix']['cmd'] = [('date'), ('date', '-R'), ('date', ['-u', '+%d %D']), + ('date', '-u "+%d %D %S"')] + commands['unix']['csubsts'] = [('date %0 "+%d %D %S"', ['-u']), + ('date %0 %1', ['-u', '+%d %D %S'])] + commands['unix']['pipe'] = ('grep', 'hello', 'hello world') + + print arg_list('cmd a1 a2 "a3 is a string" a4') + print arg_list('cmd b1 b2 "b3 is a string a4') + print arg_subst(['nothing', 'xx-%0-yyy', '%1', '%2-something'], + ['subst0', 'subst1', 'subst2']) + + e = execute(error_prefix = 'ERR: ', verbose = True) + if sys.platform == "win32": + run_tests(e, commands['windows'], False) + if os.path.exists('c:\\msys\\1.0\\bin\\sh.exe'): + e.set_shell('c:\\msys\\1.0\\bin\\sh.exe --login -c') + commands['unix']['pipe'] = ('c:\\msys\\1.0\\bin\\grep', + 'hello', 'hello world') + run_tests(e, commands['unix'], True) + else: + run_tests(e, commands['unix'], False) + del e diff --git a/rtemstoolkit/freebsd.py b/rtemstoolkit/freebsd.py new file mode 100644 index 0000000..11a827f --- /dev/null +++ b/rtemstoolkit/freebsd.py @@ -0,0 +1,99 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# This code is based on what ever doco about spec files I could find and +# RTEMS project's spec files. +# + +import pprint +import os + +import check +import execute + +def load(): + uname = os.uname() + sysctl = '/sbin/sysctl ' + e = execute.capture_execution() + exit_code, proc, output = e.shell(sysctl + 'hw.ncpu') + if exit_code == 0: + ncpus = output.split(' ')[1].strip() + else: + ncpus = '1' + if uname[4] == 'amd64': + cpu = 'x86_64' + else: + cpu = uname[4] + version = uname[2] + if version.find('-') > 0: + version = version.split('-')[0] + defines = { + '_ncpus': ('none', 'none', ncpus), + '_os': ('none', 'none', 'freebsd'), + '_host': ('triplet', 'required', cpu + '-freebsd' + version), + '_host_vendor': ('none', 'none', 'pc'), + '_host_os': ('none', 'none', 'freebsd'), + '_host_cpu': ('none', 'none', cpu), + '_host_alias': ('none', 'none', '%{nil}'), + '_host_arch': ('none', 'none', cpu), + '_usr': ('dir', 'required', '/usr/local'), + '_var': ('dir', 'optional', '/usr/local/var'), + '__bash': ('exe', 'optional', '/usr/local/bin/bash'), + '__bison': ('exe', 'required', '/usr/local/bin/bison'), + '__git': ('exe', 'required', '/usr/local/bin/git'), + '__svn': ('exe', 'required', '/usr/local/bin/svn'), + '__xz': ('exe', 'optional', '/usr/bin/xz'), + '__make': ('exe', 'required', 'gmake'), + '__patch_opts': ('none', 'none', '-E') + } + + defines['_build'] = defines['_host'] + defines['_build_vendor'] = defines['_host_vendor'] + defines['_build_os'] = defines['_host_os'] + defines['_build_cpu'] = defines['_host_cpu'] + defines['_build_alias'] = defines['_host_alias'] + defines['_build_arch'] = defines['_host_arch'] + + for gv in ['47', '48', '49']: + gcc = '%s-portbld-freebsd%s-gcc%s' % (cpu, version, gv) + if check.check_exe(gcc, gcc): + defines['__cc'] = gcc + break + for gv in ['47', '48', '49']: + gxx = '%s-portbld-freebsd%s-g++%s' % (cpu, version, gv) + if check.check_exe(gxx, gxx): + defines['__cxx'] = gxx + break + + return defines + +if __name__ == '__main__': + pprint.pprint(load()) diff --git a/rtemstoolkit/git.py b/rtemstoolkit/git.py new file mode 100644 index 0000000..2c23c05 --- /dev/null +++ b/rtemstoolkit/git.py @@ -0,0 +1,201 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Provide some basic access to the git command. +# + +import os + +import error +import execute +import log +import options +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() + if path.exists(self.path): + cwd = self.path + else: + cwd = None + cmd = [self.git] + args + log.trace('cmd: (%s) %s' % (str(cwd), ' '.join(cmd))) + exit_code, proc, output = e.spawn(cmd, cwd = path.host(cwd)) + log.trace(output) + if check: + self._git_exit_code(exit_code) + return exit_code, output + + def __init__(self, _path, opts, macros = None): + self.path = _path + self.opts = opts + if macros is None: + self.macros = opts.defaults + else: + self.macros = macros + self.git = self.macros.expand('%{__git}') + + 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 clone(self, url, _path): + ec, output = self._run(['clone', url, path.host(_path)], check = True) + + def fetch(self): + ec, output = self._run(['fetch'], check = True) + + def pull(self): + ec, output = self._run(['pull'], check = True) + + def reset(self, args): + if type(args) == str: + args = [args] + ec, output = self._run(['reset'] + args, check = True) + + def branch(self): + ec, output = self._run(['branch']) + if ec == 0: + for b in output.split('\n'): + if b[0] == '*': + return b[2:] + return None + + def checkout(self, branch = 'master'): + ec, output = self._run(['checkout', branch], check = True) + + def status(self): + _status = {} + if path.exists(self.path): + 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): + if path.exists(self.path): + ec, output = self._run(['status']) + return ec == 0 + return False + + 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 email(self): + _email = None + _name = None + ec, output = self._run(['config', '--list']) + if ec == 0: + for l in output.split('\n'): + if l.startswith('user.email'): + ls = l.split('=') + if len(ls) >= 2: + _email = ls[1] + elif l.startswith('user.name'): + ls = l.split('=') + if len(ls) >= 2: + _name = ls[1] + if _email is not None: + if _name is not None: + _email = '%s <%s>' % (_name, _email) + return _email + return None + + 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 = options.load(sys.argv) + g = repo('.', opts) + print g.git_version() + print g.valid() + print g.status() + print g.clean() + print g.remotes() + print g.email() + print g.head() diff --git a/rtemstoolkit/linux.py b/rtemstoolkit/linux.py new file mode 100644 index 0000000..022bcd0 --- /dev/null +++ b/rtemstoolkit/linux.py @@ -0,0 +1,148 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# This code is based on what ever doco about spec files I could find and +# RTEMS project's spec files. +# + +import pprint +import os + +import platform +import execute +import path + +def load(): + uname = os.uname() + smp_mflags = '' + processors = '/bin/grep processor /proc/cpuinfo' + e = execute.capture_execution() + exit_code, proc, output = e.shell(processors) + ncpus = 0 + if exit_code == 0: + try: + for l in output.split('\n'): + count = l.split(':')[1].strip() + if int(count) > ncpus: + ncpus = int(count) + except: + pass + ncpus = str(ncpus + 1) + if uname[4].startswith('arm'): + cpu = 'arm' + else: + cpu = uname[4] + + defines = { + '_ncpus': ('none', 'none', ncpus), + '_os': ('none', 'none', 'linux'), + '_host': ('triplet', 'required', cpu + '-linux-gnu'), + '_host_vendor': ('none', 'none', 'gnu'), + '_host_os': ('none', 'none', 'linux'), + '_host_cpu': ('none', 'none', cpu), + '_host_alias': ('none', 'none', '%{nil}'), + '_host_arch': ('none', 'none', cpu), + '_usr': ('dir', 'required', '/usr'), + '_var': ('dir', 'required', '/var'), + '__bzip2': ('exe', 'required', '/usr/bin/bzip2'), + '__gzip': ('exe', 'required', '/bin/gzip'), + '__tar': ('exe', 'required', '/bin/tar') + } + + # Works for LSB distros + try: + distro = platform.dist()[0] + distro_ver = float(platform.dist()[1]) + except ValueError: + # Non LSB distro found, use failover" + pass + + # Non LSB - fail over to issue + if distro == '': + try: + issue = open('/etc/issue').read() + distro = issue.split(' ')[0] + distro_ver = float(issue.split(' ')[2]) + except: + pass + + # Manage distro aliases + if distro in ['centos']: + distro = 'redhat' + elif distro in ['fedora']: + if distro_ver < 17: + distro = 'redhat' + elif distro in ['centos', 'fedora']: + distro = 'redhat' + elif distro in ['Ubuntu', 'ubuntu']: + distro = 'debian' + elif distro in ['Arch']: + distro = 'arch' + elif distro in ['SuSE']: + distro = 'suse' + + variations = { + 'debian' : { '__bzip2': ('exe', 'required', '/bin/bzip2'), + '__chgrp': ('exe', 'required', '/bin/chgrp'), + '__chown': ('exe', 'required', '/bin/chown'), + '__grep': ('exe', 'required', '/bin/grep'), + '__sed': ('exe', 'required', '/bin/sed') }, + 'redhat' : { '__bzip2': ('exe', 'required', '/bin/bzip2'), + '__chgrp': ('exe', 'required', '/bin/chgrp'), + '__chown': ('exe', 'required', '/bin/chown'), + '__install_info': ('exe', 'required', '/sbin/install-info'), + '__grep': ('exe', 'required', '/bin/grep'), + '__sed': ('exe', 'required', '/bin/sed'), + '__touch': ('exe', 'required', '/bin/touch') }, + 'fedora' : { '__chown': ('exe', 'required', '/usr/bin/chown'), + '__install_info': ('exe', 'required', '/usr/sbin/install-info') }, + 'arch' : { '__gzip': ('exe', 'required', '/usr/bin/gzip'), + '__chown': ('exe', 'required', '/usr/bin/chown') }, + 'suse' : { '__chgrp': ('exe', 'required', '/usr/bin/chgrp'), + '__chown': ('exe', 'required', '/usr/sbin/chown') }, + } + + if variations.has_key(distro): + for v in variations[distro]: + if path.exists(variations[distro][v][2]): + defines[v] = variations[distro][v] + + defines['_build'] = defines['_host'] + defines['_build_vendor'] = defines['_host_vendor'] + defines['_build_os'] = defines['_host_os'] + defines['_build_cpu'] = defines['_host_cpu'] + defines['_build_alias'] = defines['_host_alias'] + defines['_build_arch'] = defines['_host_arch'] + + return defines + +if __name__ == '__main__': + pprint.pprint(load()) diff --git a/rtemstoolkit/log.py b/rtemstoolkit/log.py new file mode 100755 index 0000000..92d3ed2 --- /dev/null +++ b/rtemstoolkit/log.py @@ -0,0 +1,226 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-testing'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Log output to stdout and/or a file. +# + +import os +import sys +import threading + +import error + +# +# A global log. +# +default = None + +# +# Global parameters. +# +tracing = False +quiet = False + +# +# Global output lock to keep output together when working with threads +# +lock = threading.Lock() + +def set_default_once(log): + if default is None: + default = log + +def _output(text = os.linesep, log = None): + """Output the text to a log if provided else send it to stdout.""" + if text is None: + text = os.linesep + if type(text) is list: + _text = '' + for l in text: + _text += l + os.linesep + text = _text + if log: + log.output(text) + elif default is not None: + default.output(text) + else: + lock.acquire() + for l in text.replace(chr(13), '').splitlines(): + print l + lock.release() + +def stderr(text = os.linesep, log = None): + lock.acquire() + for l in text.replace(chr(13), '').splitlines(): + print >> sys.stderr, l + lock.release() + +def output(text = os.linesep, log = None): + if not quiet: + _output(text, log) + +def notice(text = os.linesep, log = None, stdout_only = False): + if not quiet and \ + (default is not None and not default.has_stdout() or stdout_only): + lock.acquire() + for l in text.replace(chr(13), '').splitlines(): + print l + lock.release() + if not stdout_only: + _output(text, log) + +def trace(text = os.linesep, log = None): + if tracing: + _output(text, log) + +def warning(text = os.linesep, log = None): + for l in text.replace(chr(13), '').splitlines(): + _output('warning: %s' % (l), log) + +def flush(log = None): + if log: + log.flush() + elif default is not None: + default.flush() + +class log: + """Log output to stdout or a file.""" + def __init__(self, streams = None, tail_size = 100): + self.lock = threading.Lock() + self.tail = [] + self.tail_size = tail_size + self.fhs = [None, None] + if streams: + for s in streams: + if s == 'stdout': + self.fhs[0] = sys.stdout + elif s == 'stderr': + self.fhs[1] = sys.stderr + else: + try: + self.fhs.append(file(s, 'w')) + except IOError, ioe: + raise error.general("creating log file '" + s + \ + "': " + str(ioe)) + + def __del__(self): + for f in range(2, len(self.fhs)): + self.fhs[f].close() + + def __str__(self): + t = '' + for tl in self.tail: + t += tl + os.linesep + return t[:-len(os.linesep)] + + def _tail(self, text): + if type(text) is not list: + text = text.splitlines() + self.tail += text + if len(self.tail) > self.tail_size: + self.tail = self.tail[-self.tail_size:] + + def has_stdout(self): + return self.fhs[0] is not None + + def has_stderr(self): + return self.fhs[1] is not None + + def output(self, text): + """Output the text message to all the logs.""" + # Reformat the text to have local line types. + text = text.replace(chr(13), '').splitlines() + self._tail(text) + out = '' + for l in text: + out += l + os.linesep + self.lock.acquire() + try: + for f in range(0, len(self.fhs)): + if self.fhs[f] is not None: + self.fhs[f].write(out) + self.flush() + except: + raise + finally: + self.lock.release() + + def flush(self): + """Flush the output.""" + for f in range(0, len(self.fhs)): + if self.fhs[f] is not None: + self.fhs[f].flush() + +if __name__ == "__main__": + l = log(['stdout', 'log.txt'], tail_size = 20) + for i in range(0, 10): + l.output('log: hello world: %d\n' % (i)) + l.output('log: hello world CRLF\r\n') + l.output('log: hello world NONE') + l.flush() + print '=-' * 40 + print 'tail: %d' % (len(l.tail)) + print l + print '=-' * 40 + for i in range(0, 10): + l.output('log: hello world 2: %d\n' % (i)) + l.flush() + print '=-' * 40 + print 'tail: %d' % (len(l.tail)) + print l + print '=-' * 40 + for i in [0, 1]: + quiet = False + tracing = False + print '- quiet:%s - trace:%s %s' % (str(quiet), str(tracing), '-' * 30) + trace('trace with quiet and trace off') + notice('notice with quiet and trace off') + quiet = True + tracing = False + print '- quiet:%s - trace:%s %s' % (str(quiet), str(tracing), '-' * 30) + trace('trace with quiet on and trace off') + notice('notice with quiet on and trace off') + quiet = False + tracing = True + print '- quiet:%s - trace:%s %s' % (str(quiet), str(tracing), '-' * 30) + trace('trace with quiet off and trace on') + notice('notice with quiet off and trace on') + quiet = True + tracing = True + print '- quiet:%s - trace:%s %s' % (str(quiet), str(tracing), '-' * 30) + trace('trace with quiet on and trace on') + notice('notice with quiet on and trace on') + default = l + print '=-' * 40 + print 'tail: %d' % (len(l.tail)) + print l + print '=-' * 40 + del l diff --git a/rtemstoolkit/macros.py b/rtemstoolkit/macros.py new file mode 100644 index 0000000..d8c3175 --- /dev/null +++ b/rtemstoolkit/macros.py @@ -0,0 +1,495 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Macro tables. +# + +import copy +import re +import os +import string + +import error +import path + +# +# Macro tables +# +class macros: + + class macro_iterator: + def __init__(self, keys): + self.keys = keys + self.index = 0 + + def __iter__(self): + return self + + def next(self): + if self.index < len(self.keys): + key = self.keys[self.index] + self.index += 1 + return key + raise StopIteration + + def iterkeys(self): + return self.keys + + def __init__(self, name = None, original = None, rtdir = '.'): + self.files = [] + self.macro_filter = re.compile(r'%{[^}]+}') + if original is None: + self.macros = {} + self.read_maps = [] + self.read_map_locked = False + self.write_map = 'global' + self.macros['global'] = {} + self.macros['global']['nil'] = ('none', 'none', '') + self.macros['global']['_cwd'] = ('dir', 'required', path.abspath(os.getcwd())) + self.macros['global']['_rtdir'] = ('dir', 'required', path.abspath(rtdir)) + self.macros['global']['_rttop'] = ('dir', 'required', path.abspath(path.dirname(rtdir))) + else: + self.macros = {} + for m in original.macros: + if m not in self.macros: + self.macros[m] = {} + for k in original.macros[m]: + self.macros[m][k] = copy.copy(original.macros[m][k]) + self.read_maps = sorted(copy.copy(original.read_maps)) + self.read_map_locked = copy.copy(original.read_map_locked) + self.write_map = copy.copy(original.write_map) + if name is not None: + self.load(name) + + def __copy__(self): + return macros(original = self) + + def __str__(self): + text_len = 80 + text = '' + for f in self.files: + text += '> %s%s' % (f, os.linesep) + for map in self.macros: + if map in self.read_maps: + if self.read_map_locked: + rm = 'R' + else: + rm = 'r' + else: + rm = '-' + if map == self.write_map: + wm = 'w' + else: + wm = '-' + text += '[%s] %s%s%s' % (map, rm, wm, os.linesep) + for k in sorted(self.macros[map].keys()): + d = self.macros[map][k] + text += " %s:%s '%s'%s '%s'%s" % \ + (k, ' ' * (20 - len(k)), + d[0], ' ' * (8 - len(d[0])), + d[1], ' ' * (10 - len(d[1]))) + if len(d[2]) == 0: + text += "''%s" % (os.linesep) + else: + if '\n' in d[2]: + text += "'''" + else: + text += "'" + indent = False + ds = d[2].split('\n') + lc = 0 + for l in ds: + lc += 1 + while len(l): + if indent: + text += ' %21s %10s %12s' % (' ', ' ', ' ') + text += l[0:text_len] + l = l[text_len:] + if len(l): + text += ' \\' + elif lc == len(ds): + if len(ds) > 1: + text += "'''" + else: + text += "'" + text += '%s' % (os.linesep) + indent = True + return text + + def __iter__(self): + return macros.macro_iterator(self.keys()) + + def __getitem__(self, key): + macro = self.get(key) + if macro is None or macro[1] == 'undefine': + raise IndexError('key: %s' % (key)) + return macro[2] + + def __setitem__(self, key, value): + if type(key) is not str: + raise TypeError('bad key type (want str): %s' % (type(key))) + if type(value) is str: + value = ('none', 'none', value) + if type(value) is not tuple: + raise TypeError('bad value type (want tuple): %s' % (type(value))) + if len(value) != 3: + raise TypeError('bad value tuple (len not 3): %d' % (len(value))) + if type(value[0]) is not str: + raise TypeError('bad value tuple type field: %s' % (type(value[0]))) + if type(value[1]) is not str: + raise TypeError('bad value tuple attrib field: %s' % (type(value[1]))) + if type(value[2]) is not str: + raise TypeError('bad value tuple value field: %s' % (type(value[2]))) + if value[0] not in ['none', 'triplet', 'dir', 'file', 'exe']: + raise TypeError('bad value tuple (type field): %s' % (value[0])) + if value[1] not in ['none', 'optional', 'required', + 'override', 'undefine', 'convert']: + raise TypeError('bad value tuple (attrib field): %s' % (value[1])) + if value[1] == 'convert': + value = self.expand(value) + self.macros[self.write_map][self.key_filter(key)] = value + + def __delitem__(self, key): + self.undefine(key) + + def __contains__(self, key): + return self.has_key(key) + + def __len__(self): + return len(self.keys()) + + def keys(self): + keys = self.macros['global'].keys() + for rm in self.get_read_maps(): + for mk in self.macros[rm]: + if self.macros[rm][mk][1] == 'undefine': + if mk in keys: + keys.remove(mk) + else: + keys.append(mk) + return sorted(set(keys)) + + def has_key(self, key): + if type(key) is not str: + raise TypeError('bad key type (want str): %s' % (type(key))) + if self.key_filter(key) not in self.keys(): + return False + return True + + def maps(self): + return self.macros.keys() + + def get_read_maps(self): + return [rm[5:] for rm in self.read_maps] + + def key_filter(self, key): + if key.startswith('%{') and key[-1] is '}': + key = key[2:-1] + return key.lower() + + def parse(self, lines): + + def _clean(l): + if '#' in l: + l = l[:l.index('#')] + if '\r' in l: + l = l[:l.index('r')] + if '\n' in l: + l = l[:l.index('\n')] + return l.strip() + + trace_me = False + if trace_me: + print '[[[[]]]] parsing macros' + orig_macros = copy.copy(self.macros) + map = 'global' + lc = 0 + state = 'key' + token = '' + macro = [] + for l in lines: + lc += 1 + #print 'l:%s' % (l[:-1]) + if len(l) == 0: + continue + l_remaining = l + for c in l: + if trace_me: + print ']]]]]]]] c:%s(%d) s:%s t:"%s" m:%r M:%s' % \ + (c, ord(c), state, token, macro, map) + l_remaining = l_remaining[1:] + if c is '#' and not state.startswith('value'): + break + if c == '\n' or c == '\r': + if not (state is 'key' and len(token) == 0) and \ + not state.startswith('value-multiline'): + self.macros = orig_macros + raise error.general('malformed macro line:%d: %s' % (lc, l)) + if state is 'key': + if c not in string.whitespace: + if c is '[': + state = 'map' + elif c is '%': + state = 'directive' + elif c is ':': + macro += [token] + token = '' + state = 'attribs' + elif c is '#': + break + else: + token += c + elif state is 'map': + if c is ']': + if token not in self.macros: + self.macros[token] = {} + map = token + token = '' + state = 'key' + elif c in string.printable and c not in string.whitespace: + token += c + else: + self.macros = orig_macros + raise error.general('invalid macro map:%d: %s' % (lc, l)) + elif state is 'directive': + if c in string.whitespace: + if token == 'include': + self.load(_clean(l_remaining)) + token = '' + state = 'key' + break + elif c in string.printable and c not in string.whitespace: + token += c + else: + self.macros = orig_macros + raise error.general('invalid macro directive:%d: %s' % (lc, l)) + elif state is 'include': + if c is string.whitespace: + if token == 'include': + state = 'include' + elif c in string.printable and c not in string.whitespace: + token += c + else: + self.macros = orig_macros + raise error.general('invalid macro directive:%d: %s' % (lc, l)) + elif state is 'attribs': + if c not in string.whitespace: + if c is ',': + macro += [token] + token = '' + if len(macro) == 3: + state = 'value-start' + else: + token += c + elif state is 'value-start': + if c is "'": + state = 'value-line-start' + elif state is 'value-line-start': + if c is "'": + state = 'value-multiline-start' + else: + state = 'value-line' + token += c + elif state is 'value-multiline-start': + if c is "'": + state = 'value-multiline' + else: + macro += [token] + state = 'macro' + elif state is 'value-line': + if c is "'": + macro += [token] + state = 'macro' + else: + token += c + elif state is 'value-multiline': + if c is "'": + state = 'value-multiline-end' + else: + token += c + elif state is 'value-multiline-end': + if c is "'": + state = 'value-multiline-end-end' + else: + state = 'value-multiline' + token += "'" + c + elif state is 'value-multiline-end-end': + if c is "'": + macro += [token] + state = 'macro' + else: + state = 'value-multiline' + token += "''" + c + else: + self.macros = orig_macros + raise error.internal('bad state: %s' % (state)) + if state is 'macro': + self.macros[map][macro[0].lower()] = (macro[1], macro[2], macro[3]) + macro = [] + token = '' + state = 'key' + + def load(self, name): + names = self.expand(name).split(':') + for n in names: + if path.exists(n): + try: + mc = open(path.host(n), 'r') + macros = self.parse(mc) + mc.close() + self.files += [n] + return + except IOError, err: + pass + raise error.general('opening macro file: %s' % \ + (path.host(self.expand(name)))) + + def get(self, key): + if type(key) is not str: + raise TypeError('bad key type: %s' % (type(key))) + key = self.key_filter(key) + for rm in self.get_read_maps(): + if key in self.macros[rm]: + return self.macros[rm][key] + if key in self.macros['global']: + return self.macros['global'][key] + return None + + def get_type(self, key): + m = self.get(key) + if m is None: + return None + return m[0] + + def get_attribute(self, key): + m = self.get(key) + if m is None: + return None + return m[1] + + def get_value(self, key): + m = self.get(key) + if m is None: + return None + return m[2] + + def overridden(self, key): + return self.get_attribute(key) == 'override' + + def define(self, key, value = '1'): + if type(key) is not str: + raise TypeError('bad key type: %s' % (type(key))) + self.__setitem__(key, ('none', 'none', value)) + + def undefine(self, key): + if type(key) is not str: + raise TypeError('bad key type: %s' % (type(key))) + key = self.key_filter(key) + for map in self.macros: + if key in self.macros[map]: + del self.macros[map][key] + + def expand(self, _str): + """Simple basic expander of config file macros.""" + expanded = True + while expanded: + expanded = False + for m in self.macro_filter.findall(_str): + name = m[2:-1] + macro = self.get(name) + if macro is None: + print self.macros + raise error.general('cannot expand default macro: %s in "%s"' % + (m, _str)) + _str = _str.replace(m, macro[2]) + expanded = True + return _str + + def find(self, regex): + what = re.compile(regex) + keys = [] + for key in self.keys(): + if what.match(key): + keys += [key] + return keys + + def set_read_map(self, _map): + if not self.read_map_locked: + if _map in self.macros: + if _map not in self.get_read_maps(): + rm = '%04d_%s' % (len(self.read_maps), _map) + self.read_maps = sorted(self.read_maps + [rm]) + return True + return False + + def unset_read_map(self, _map): + if not self.read_map_locked: + if _map in self.get_read_maps(): + for i in range(0, len(self.read_maps)): + if '%04d_%s' % (i, _map) == self.read_maps[i]: + self.read_maps.pop(i) + return True + return False + + def set_write_map(self, map): + if map in self.macros: + self.write_map = map + return True + return False + + def lock_read_map(self): + self.read_map_locked = True + + def unlock_read_map(self): + self.read_map_locked = False + +if __name__ == "__main__": + import copy + import sys + m = macros(name = 'defaults.mc') + d = copy.copy(m) + m['test1'] = 'something' + if d.has_key('test1'): + print 'error: copy failed.' + sys.exit(1) + m.parse("[test]\n" \ + "test1: none, undefine, ''\n" \ + "name: none, override, 'pink'\n") + print 'set test:', m.set_read_map('test') + if m['name'] != 'pink': + print 'error: override failed. name is %s' % (m['name']) + sys.exit(1) + if m.has_key('test1'): + print 'error: map undefine failed.' + sys.exit(1) + print 'unset test:', m.unset_read_map('test') + print m + print m.keys() diff --git a/rtemstoolkit/mailer.py b/rtemstoolkit/mailer.py new file mode 100644 index 0000000..df42580 --- /dev/null +++ b/rtemstoolkit/mailer.py @@ -0,0 +1,120 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2013-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Manage emailing results or reports. +# + +import os +import smtplib +import socket + +import error +import options +import path + +def append_options(opts): + opts['--mail'] = 'Send email report or results.' + opts['--smtp-host'] = 'SMTP host to send via.' + opts['--mail-to'] = 'Email address to send the email too.' + opts['--mail-from'] = 'Email address the report is from.' + +class mail: + def __init__(self, opts): + self.opts = opts + + def from_address(self): + + def _clean(l): + if '#' in l: + l = l[:l.index('#')] + if '\r' in l: + l = l[:l.index('r')] + if '\n' in l: + l = l[:l.index('\n')] + return l.strip() + + addr = self.opts.get_arg('--mail-from') + if addr is not None: + return addr[1] + mailrc = None + if 'MAILRC' in os.environ: + mailrc = os.environ['MAILRC'] + if mailrc is None and 'HOME' in os.environ: + mailrc = path.join(os.environ['HOME'], '.mailrc') + if mailrc is not None and path.exists(mailrc): + # set from="Joe Blow <joe@blow.org>" + try: + mrc = open(mailrc, 'r') + lines = mrc.readlines() + mrc.close() + except IOError, err: + raise error.general('error reading: %s' % (mailrc)) + for l in lines: + l = _clean(l) + if 'from' in l: + fa = l[l.index('from') + len('from'):] + if '=' in fa: + addr = fa[fa.index('=') + 1:].replace('"', ' ').strip() + if addr is not None: + return addr + addr = self.opts.defaults.get_value('%{_sbgit_mail}') + return addr + + def smtp_host(self): + host = self.opts.get_arg('--smtp-host') + if host is not None: + return host[1] + host = self.opts.defaults.get_value('%{_mail_smtp_host}') + if host is not None: + return host + return 'localhost' + + def send(self, to_addr, subject, body): + from_addr = self.from_address() + msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n" % \ + (from_addr, to_addr, subject) + body + try: + s = smtplib.SMTP(self.smtp_host()) + s.sendmail(from_addr, [to_addr], msg) + except smtplib.SMTPException, se: + raise error.general('sending mail: %s' % (str(se))) + except socket.error, se: + raise error.general('sending mail: %s' % (str(se))) + +if __name__ == '__main__': + import sys + optargs = {} + append_options(optargs) + opts = options.load(sys.argv, optargs = optargs, defaults = 'defaults.mc') + m = mail(opts) + print 'From: %s' % (m.from_address()) + print 'SMTP Host: %s' % (m.smtp_host()) + m.send(m.from_address(), 'Test mailer.py', 'This is a test') diff --git a/rtemstoolkit/options.py b/rtemstoolkit/options.py new file mode 100644 index 0000000..97b8ba7 --- /dev/null +++ b/rtemstoolkit/options.py @@ -0,0 +1,597 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Determine the defaults and load the specific file. +# + +import copy +import glob +import pprint +import re +import os +import string + +import error +import execute +import git +import log +import macros +import path +import sys + +import version + +basepath = 'tb' + +# +# Save the host state. +# +host_windows = False + +class command_line(object): + """Process the command line in a common way for all Tool Builder commands.""" + + def __init__(self, base_path = None, argv = None, optargs = None, + defaults = None, long_opts = None, long_opts_help = None, + command_path = None, log_default = None): + + if argv is None: + return + + global basepath + + if long_opts == None: + raise error.general('No options provided') + + basepath = base_path + + if log_default is not None and type(log_default) is not list: + raise error.general('log default is a list') + self.log_default = log_default + + self.long_opts = { + # key macro handler param defs init + '--jobs' : ('_jobs', self._lo_jobs, True, 'default', True), + '--log' : ('_logfile', self._lo_string, True, None, False), + '--macros' : ('_macros', self._lo_string, True, None, False), + '--force' : ('_force', self._lo_bool, False, '0', True), + '--quiet' : ('_quiet', self._lo_bool, False, '0', True), + '--trace' : ('_trace', self._lo_bool, False, '0', True), + '--dry-run' : ('_dry_run', self._lo_bool, False, '0', True), + '--warn-all' : ('_warn_all', self._lo_bool, False, '0', True), + '--no-clean' : ('_no_clean', self._lo_bool, False, '0', True), + '--keep-going' : ('_keep_going', self._lo_bool, False, '0', True), + '--always-clean' : ('_always_clean', self._lo_bool, False, '0', True), + '--no-install' : ('_no_install', self._lo_bool, False, '0', True), + '--help' : (None, self._lo_help, False, None, False) + } + self.long_opts_help = { + '--force': 'Force the build to proceed', + '--quiet': 'Quiet output (not used)', + '--trace': 'Trace the execution', + '--dry-run': 'Do everything but actually run the build', + '--warn-all': 'Generate warnings', + '--no-clean': 'Do not clean up the build tree', + '--always-clean': 'Always clean the build tree, even with an error', + '--keep-going': 'Do not stop on an error.', + '--jobs=[0..n,none,half,full]': 'Run with specified number of jobs, default: num CPUs.', + '--macros file[,file]': 'Macro format files to load after the defaults', + '--log file': 'Log file where all build out is written too', + } + self.opts = { 'params' : [] } + self.command_path = command_path + self.command_name = path.basename(argv[0]) + self.argv = argv + self.args = argv[1:] + self.optargs = optargs + self.defaults = defaults + for lo in self.long_opts: + self.opts[lo[2:]] = self.long_opts[lo][3] + if self.long_opts[lo][4]: + self.defaults[self.long_opts[lo][0]] = ('none', 'none', self.long_opts[lo][3]) + for lo in long_opts: + if lo in self.long_opts: + raise error.general('suplicate option: %s' % (lo)) + self.opts[lo[2:]] = long_opts[lo][3] + if long_opts[lo][4]: + self.defaults[long_opts[lo][0]] = ('none', 'none', long_opts[lo][3]) + if long_opts[lo][1] == 'int': + handler = self._lo_int + elif long_opts[lo][1] == 'string': + handler = self._lo_string + elif long_opts[lo][1] == 'path': + hanlder = self._lo_path + elif long_opts[lo][1] == 'jobs': + handler = self._lo_jobs + elif long_opts[lo][1] == 'bool': + handler = self._lo_bool + elif long_opts[lo][1] == 'triplet': + handler = self._lo_triplets + else: + raise error.general('invalid option handler: %s: %s' % (lo, long_opts[lo][1])) + self.long_opts[lo] = (long_opts[lo][0], handler, long_opts[lo][2], + long_opts[lo][3], long_opts[lo][4]) + if lo not in long_opts_help: + raise error.general('no help for option: %s' % (lo)) + self.long_opts_help[lo] = long_opts_help[lo] + + def __copy__(self): + new = type(self)() + #new.__dict__.update(self.__dict__) + new.opts = copy.copy(self.opts) + new.command_path = copy.copy(self.command_path) + new.command_name = copy.copy(self.command_name) + new.argv = self.argv + new.args = self.args + new.optargs = copy.copy(self.optargs) + new.defaults = copy.copy(self.defaults) + new.long_opts = copy.copy(self.long_opts) + return new + + def __str__(self): + def _dict(dd): + s = '' + ddl = dd.keys() + ddl.sort() + for d in ddl: + s += ' ' + d + ': ' + str(dd[d]) + '\n' + return s + + s = 'command: ' + self.command() + \ + '\nargs: ' + str(self.args) + \ + '\nopts:\n' + _dict(self.opts) + + return s + + def _lo_int(self, opt, macro, value): + if value is None: + raise error.general('option requires a value: %s' % (opt)) + try: + num = int(value) + except: + raise error.general('option conversion to int failed: %s' % (opt)) + self.opts[opt[2:]] = value + self.defaults[macro] = value + + def _lo_string(self, opt, macro, value): + if value is None: + raise error.general('option requires a value: %s' % (opt)) + self.opts[opt[2:]] = value + self.defaults[macro] = value + + def _lo_path(self, opt, macro, value): + if value is None: + raise error.general('option requires a path: %s' % (opt)) + value = path.abspath(value) + self.opts[opt[2:]] = value + self.defaults[macro] = value + + def _lo_jobs(self, opt, macro, value): + if value is None: + raise error.general('option requires a value: %s' % (opt)) + ok = False + if value in ['max', 'none', 'half']: + ok = True + else: + try: + i = int(value) + ok = True + except: + pass + if not ok: + try: + f = float(value) + ok = True + except: + pass + if not ok: + raise error.general('invalid jobs option: %s' % (value)) + self.defaults[macro] = value + self.opts[opt[2:]] = value + + def _lo_bool(self, opt, macro, value): + if value is not None: + raise error.general('option does not take a value: %s' % (opt)) + self.opts[opt[2:]] = '1' + self.defaults[macro] = '1' + + def _lo_triplets(self, opt, macro, value): + # + # This is a target triplet. Run it past config.sub to make make sure it + # is ok. The target triplet is 'cpu-vendor-os'. + # + e = execute.capture_execution() + config_sub = path.join(self.command_path, + basepath, 'config.sub') + exit_code, proc, output = e.shell(config_sub + ' ' + value) + if exit_code == 0: + value = output + self.defaults[macro] = ('triplet', 'none', value) + self.opts[opt[2:]] = value + _cpu = macro + '_cpu' + _arch = macro + '_arch' + _vendor = macro + '_vendor' + _os = macro + '_os' + _arch_value = '' + _vendor_value = '' + _os_value = '' + dash = value.find('-') + if dash >= 0: + _arch_value = value[:dash] + value = value[dash + 1:] + dash = value.find('-') + if dash >= 0: + _vendor_value = value[:dash] + value = value[dash + 1:] + if len(value): + _os_value = value + self.defaults[_cpu] = _arch_value + self.defaults[_arch] = _arch_value + self.defaults[_vendor] = _vendor_value + self.defaults[_os] = _os_value + + def _lo_help(self, opt, macro, value): + self.help() + + def _help_indent(self): + indent = 0 + if self.optargs: + for o in self.optargs: + if len(o) > indent: + indent = len(o) + for o in self.long_opts_help: + if len(o) > indent: + indent = len(o) + return indent + + def help(self): + print '%s: [options] [args]' % (self.command_name) + print 'RTEMS Tools Project (c) 2012-2014 Chris Johns' + print 'Options and arguments:' + opts = self.long_opts_help.keys() + if self.optargs: + opts += self.optargs.keys() + indent = self._help_indent() + for o in sorted(opts): + if o in self.long_opts_help: + h = self.long_opts_help[o] + elif self.optargs: + h = self.optargs[o] + else: + raise error.general('invalid help data: %s' %(o)) + print '%-*s : %s' % (indent, o, h) + raise error.exit() + + def process(self): + arg = 0 + while arg < len(self.args): + a = self.args[arg] + if a == '-?': + self.help() + elif a.startswith('--'): + los = a.split('=') + lo = los[0] + if lo in self.long_opts: + long_opt = self.long_opts[lo] + if len(los) == 1: + if long_opt[2]: + if arg == len(self.args) - 1: + raise error.general('option requires a parameter: %s' % (lo)) + arg += 1 + value = self.args[arg] + else: + value = None + else: + value = '='.join(los[1:]) + long_opt[1](lo, long_opt[0], value) + else: + self.opts['params'].append(a) + arg += 1 + + def post_process(self): + # Handle the log first. + log.default = log.log(self.logfiles()) + if self.trace(): + log.tracing = True + if self.quiet(): + log.quiet = True + # Handle the jobs for make + if '_ncpus' not in self.defaults: + raise error.general('host number of CPUs not set') + ncpus = self.jobs(self.defaults['_ncpus']) + if ncpus > 1: + self.defaults['_smp_mflags'] = '-j %d' % (ncpus) + else: + self.defaults['_smp_mflags'] = self.defaults['nil'] + # Load user macro files + um = self.user_macros() + if um: + checked = path.exists(um) + if False in checked: + raise error.general('macro file not found: %s' % (um[checked.index(False)])) + for m in um: + self.defaults.load(m) + # Check if the user has a private set of macros to load + if 'RSB_MACROS' in os.environ: + if path.exists(os.environ['RSB_MACROS']): + self.defaults.load(os.environ['RSB_MACROS']) + if 'HOME' in os.environ: + rsb_macros = path.join(os.environ['HOME'], '.rsb_macros') + if path.exists(rsb_macros): + self.defaults.load(rsb_macros) + + def local_git(self): + repo = git.repo(self.defaults.expand('%{_rtdir}'), self) + if repo.valid(): + repo_valid = '1' + repo_head = repo.head() + repo_clean = repo.clean() + repo_id = repo_head + if not repo_clean: + repo_id += '-modified' + repo_mail = repo.email() + else: + repo_valid = '0' + repo_head = '%{nil}' + repo_clean = '%{nil}' + repo_id = 'no-repo' + repo_mail = None + self.defaults['_local_git_valid'] = repo_valid + self.defaults['_local_git_head'] = repo_head + self.defaults['_local_git_clean'] = str(repo_clean) + self.defaults['_local_git_id'] = repo_id + if repo_mail is not None: + self.defaults['_localgit_mail'] = repo_mail + + def command(self): + return path.join(self.command_path, self.command_name) + + def force(self): + return self.opts['force'] != '0' + + def dry_run(self): + return self.opts['dry-run'] != '0' + + def set_dry_run(self): + self.opts['dry-run'] = '1' + + def quiet(self): + return self.opts['quiet'] != '0' + + def trace(self): + return self.opts['trace'] != '0' + + def warn_all(self): + return self.opts['warn-all'] != '0' + + def keep_going(self): + return self.opts['keep-going'] != '0' + + def no_clean(self): + return self.opts['no-clean'] != '0' + + def always_clean(self): + return self.opts['always-clean'] != '0' + + def no_install(self): + return self.opts['no-install'] != '0' + + def user_macros(self): + # + # Return something even if it does not exist. + # + if self.opts['macros'] is None: + return None + um = [] + configs = self.defaults.expand('%{_configdir}').split(':') + for m in self.opts['macros'].split(','): + if path.exists(m): + um += [m] + else: + # Get the expanded config macros then check them. + cm = path.expand(m, configs) + ccm = path.exists(cm) + if True in ccm: + # Pick the first found + um += [cm[ccm.index(True)]] + else: + um += [m] + return um if len(um) else None + + def jobs(self, cpus): + try: + cpus = int(cpus) + except: + raise error.general('invalid host cpu value') + opt_jobs = self.opts['jobs'] + if opt_jobs == 'default': + _jobs = self.defaults.get_value('jobs') + if _jobs is not None: + if _jobs == 'none': + cpus = 0 + elif _jobs == 'max': + pass + elif _jobs == 'half': + cpus = cpus / 2 + else: + try: + cpus = int(_jobs) + except: + raise error.general('invalid %%{jobs} value: %s' % (_jobs)) + else: + opt_jobs = 'max' + if opt_jobs != 'default': + if opt_jobs == 'none': + cpus = 0 + elif opt_jobs == 'max': + pass + elif opt_jobs == 'half': + cpus = cpus / 2 + else: + ok = False + try: + i = int(opt_jobs) + cpus = i + ok = True + except: + pass + if not ok: + try: + f = float(opt_jobs) + cpus = f * cpus + ok = True + except: + pass + if not ok: + raise error.internal('bad jobs option: %s' % (opt_jobs)) + if cpus <= 0: + cpu = 1 + return cpus + + def params(self): + return self.opts['params'] + + def get_args(self): + for arg in self.args: + yield arg + + def find_arg(self, arg): + if self.optargs is None or arg not in self.optargs: + raise error.internal('bad arg: %s' % (arg)) + for a in self.args: + sa = a.split('=') + if sa[0].startswith(arg): + return sa + return None + + def logfiles(self): + if 'log' in self.opts and self.opts['log'] is not None: + log = self.opts['log'].split(',') + elif self.log_default is None: + log = ['stdout'] + else: + log = self.log_default + return log + + def urls(self): + if self.opts['url'] is not None: + return self.opts['url'].split(',') + return None + + def log_info(self): + log.output(' Command Line: %s' % (' '.join(self.argv))) + log.output(' Python: %s' % (sys.version.replace('\n', ''))) + +def load(opts): + """ + Copy the defaults, get the host specific values and merge them overriding + any matching defaults, then create an options object to handle the command + line merging in any command line overrides. Finally post process the + command line. + """ + + global host_windows + + overrides = None + if os.name == 'nt': + try: + import windows + overrides = windows.load() + host_windows = True + except: + raise error.general('failed to load Windows host support') + elif os.name == 'posix': + uname = os.uname() + try: + if uname[0].startswith('CYGWIN_NT'): + import windows + overrides = windows.load() + elif uname[0] == 'Darwin': + import darwin + overrides = darwin.load() + elif uname[0] == 'FreeBSD': + import freebsd + overrides = freebsd.load() + elif uname[0] == 'NetBSD': + import netbsd + overrides = netbsd.load() + elif uname[0] == 'Linux': + import linux + overrides = linux.load() + except: + raise error.general('failed to load %s host support' % (uname[0])) + else: + raise error.general('unsupported host type; please add') + if overrides is None: + raise error.general('no hosts defaults found; please add') + for k in overrides: + opts.defaults[k] = overrides[k] + + opts.local_git() + opts.process() + opts.post_process() + +def run(args): + try: + long_opts = { + # key macro handler param defs init + '--test-path' : ('_test_path', 'path', True, None, False), + '--test-jobs' : ('_test_jobs', 'jobs', True, 'max', True), + '--test-bool' : ('_test_bool', 'bool', False, '0', True) + } + opts = command_line(base_path = '.', + argv = args, + optargs = None, + defaults = macros.macros(), + long_opts = long_opts, + command_path = '.') + load(opts) + log.notice('RTEMS Tools Project - Defaults, v%s' % (version.str())) + opts.log_info() + log.notice('Options:') + log.notice(str(opts)) + log.notice('Defaults:') + log.notice(str(opts.defaults)) + except error.general, gerr: + print gerr + sys.exit(1) + except error.internal, ierr: + print ierr + sys.exit(1) + except error.exit, eerr: + pass + except KeyboardInterrupt: + _notice(opts, 'abort: user terminated') + sys.exit(1) + sys.exit(0) + +if __name__ == '__main__': + run(sys.argv) diff --git a/rtemstoolkit/path.py b/rtemstoolkit/path.py new file mode 100644 index 0000000..238e6d9 --- /dev/null +++ b/rtemstoolkit/path.py @@ -0,0 +1,239 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Manage paths locally. The internally the path is in Unix or shell format and +# we convert to the native format when performing operations at the Python +# level. This allows macro expansion to work. +# + +import glob +import log +import os +import shutil +import string + +import error + +windows = os.name == 'nt' + +def host(path): + if path is not None: + while '//' in path: + path = path.replace('//', '/') + if windows and len(path) > 2: + if path[0] == '/' and path[2] == '/' and \ + (path[1] in string.ascii_lowercase or \ + path[1] in string.ascii_uppercase): + path = ('%s:%s' % (path[1], path[2:])).replace('/', '\\') + return path + +def shell(path): + if path is not None: + if windows and len(path) > 1 and path[1] == ':': + path = ('/%s%s' % (path[0], path[2:])).replace('\\', '/') + while '//' in path: + path = path.replace('//', '/') + return path + +def basename(path): + return shell(os.path.basename(path)) + +def dirname(path): + return shell(os.path.dirname(path)) + +def join(path, *args): + path = shell(path) + for arg in args: + if len(path): + path += '/' + shell(arg) + else: + path = shell(arg) + return shell(path) + +def abspath(path): + return shell(os.path.abspath(host(path))) + +def splitext(path): + root, ext = os.path.splitext(host(path)) + return shell(root), ext + +def exists(paths): + if type(paths) == list: + results = [] + for p in paths: + results += [os.path.exists(host(p))] + return results + return os.path.exists(host(paths)) + +def isdir(path): + return os.path.isdir(host(path)) + +def isfile(path): + return os.path.isfile(host(path)) + +def isabspath(path): + return path[0] == '/' + +def iswritable(path): + return os.access(host(path), os.W_OK) + +def ispathwritable(path): + path = host(path) + while len(path) != 0: + if os.path.exists(path): + return iswritable(path) + path = os.path.dirname(path) + return False + +def mkdir(path): + path = host(path) + if exists(path): + if not isdir(path): + raise error.general('path exists and is not a directory: %s' % (path)) + else: + if windows: + try: + os.makedirs(host(path)) + except IOError, err: + raise error.general('cannot make directory: %s' % (path)) + except OSError, err: + raise error.general('cannot make directory: %s' % (path)) + except WindowsError, err: + raise error.general('cannot make directory: %s' % (path)) + else: + try: + os.makedirs(host(path)) + except IOError, err: + raise error.general('cannot make directory: %s' % (path)) + except OSError, err: + raise error.general('cannot make directory: %s' % (path)) + +def removeall(path): + + def _onerror(function, path, excinfo): + print 'removeall error: (%s) %s' % (excinfo, path) + + path = host(path) + shutil.rmtree(path, onerror = _onerror) + return + +def expand(name, paths): + l = [] + for p in paths: + l += [join(p, name)] + return l + +def collect_files(path_): + # + # Convert to shell paths and return shell paths. + # + # @fixme should this use a passed in set of defaults and not + # not the initial set of values ? + # + path_ = shell(path_) + if '*' in path_ or '?' in path_: + dir = dirname(path_) + base = basename(path_) + if len(base) == 0: + base = '*' + files = [] + for p in dir.split(':'): + hostdir = host(p) + for f in glob.glob(os.path.join(hostdir, base)): + files += [host(f)] + else: + files = [host(path_)] + return sorted(files) + +def copy_tree(src, dst): + hsrc = host(src) + hdst = host(dst) + + if os.path.exists(src): + names = os.listdir(src) + else: + name = [] + + if not os.path.isdir(dst): + os.makedirs(dst) + + for name in names: + srcname = os.path.join(src, name) + dstname = os.path.join(dst, name) + try: + if os.path.islink(srcname): + linkto = os.readlink(srcname) + if os.path.exists(dstname): + if os.path.islink(dstname): + dstlinkto = os.readlink(dstname) + if linkto != dstlinkto: + log.warning('copying tree: update of link does not match: %s -> %s' % \ + (dstname, dstlinkto)) + os.remove(dstname) + else: + log.warning('copying tree: destination is not a link: %s' % \ + (dstname)) + os.remove(dstname) + else: + os.symlink(linkto, dstname) + elif os.path.isdir(srcname): + copy_tree(srcname, dstname) + else: + shutil.copy2(srcname, dstname) + except shutil.Error, err: + raise error.general('copying tree: %s -> %s: %s' % (src, dst, str(err))) + except EnvironmentError, why: + raise error.general('copying tree: %s -> %s: %s' % (srcname, dstname, str(why))) + try: + shutil.copystat(src, dst) + except OSError, why: + ok = False + if windows: + if WindowsError is not None and isinstance(why, WindowsError): + ok = True + if not ok: + raise error.general('copying tree: %s -> %s: %s' % (src, dst, str(why))) + +if __name__ == '__main__': + print host('/a/b/c/d-e-f') + print host('//a/b//c/d-e-f') + print shell('/w/x/y/z') + print basename('/as/sd/df/fg/me.txt') + print dirname('/as/sd/df/fg/me.txt') + print join('/d', 'g', '/tyty/fgfg') + windows = True + print host('/a/b/c/d-e-f') + print host('//a/b//c/d-e-f') + print shell('/w/x/y/z') + print shell('w:/x/y/z') + print basename('x:/sd/df/fg/me.txt') + print dirname('x:/sd/df/fg/me.txt') + print join('s:/d/', '/g', '/tyty/fgfg') diff --git a/rtemstoolkit/version.py b/rtemstoolkit/version.py new file mode 100644 index 0000000..a3cf5a8 --- /dev/null +++ b/rtemstoolkit/version.py @@ -0,0 +1,48 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Manage paths locally. The internally the path is in Unix or shell format and +# we convert to the native format when performing operations at the Python +# level. This allows macro expansion to work. +# + +major = 0 +minor = 0 +revision = 0 + +def str(): + return '%d.%d.%d'% (major, minor, revision) + +if __name__ == '__main__': + print 'major = %d' % (major) + print 'minor = %d' % (minor) + print 'revision = %d' % (revision) + print 'Version: %s' % (str()) diff --git a/rtemstoolkit/windows.py b/rtemstoolkit/windows.py new file mode 100644 index 0000000..be4e1f6 --- /dev/null +++ b/rtemstoolkit/windows.py @@ -0,0 +1,135 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Windows specific support and overrides. +# + +import error +import pprint +import os + +import execute + +def load(): + # Default to the native Windows Python. + uname = 'win32' + system = 'mingw32' + if os.environ.has_key('HOSTTYPE'): + hosttype = os.environ['HOSTTYPE'] + else: + hosttype = 'i686' + host_triple = hosttype + '-pc-' + system + build_triple = hosttype + '-pc-' + system + + # See if this is actually Cygwin Python + if os.name == 'posix': + try: + uname = os.uname() + hosttype = uname[4] + uname = uname[0] + if uname.startswith('CYGWIN'): + if uname.endswith('WOW64'): + uname = 'cygwin' + build_triple = hosttype + '-pc-' + uname + hosttype = 'x86_64' + host_triple = hosttype + '-w64-' + system + else: + raise error.general('invalid uname for Windows') + else: + raise error.general('invalid POSIX python') + except: + pass + + if os.environ.has_key('NUMBER_OF_PROCESSORS'): + ncpus = os.environ['NUMBER_OF_PROCESSORS'] + else: + ncpus = '1' + + defines = { + '_ncpus': ('none', 'none', ncpus), + '_os': ('none', 'none', 'win32'), + '_build': ('triplet', 'required', build_triple), + '_build_vendor': ('none', 'none', 'microsoft'), + '_build_os': ('none', 'none', 'win32'), + '_build_cpu': ('none', 'none', hosttype), + '_build_alias': ('none', 'none', '%{nil}'), + '_build_arch': ('none', 'none', hosttype), + '_host': ('triplet', 'required', host_triple), + '_host_vendor': ('none', 'none', 'microsoft'), + '_host_os': ('none', 'none', 'win32'), + '_host_cpu': ('none', 'none', hosttype), + '_host_alias': ('none', 'none', '%{nil}'), + '_host_arch': ('none', 'none', hosttype), + '_usr': ('dir', 'optional', '/opt/local'), + '_var': ('dir', 'optional', '/opt/local/var'), + '__bash': ('exe', 'required', 'bash'), + '__bzip2': ('exe', 'required', 'bzip2'), + '__bison': ('exe', 'required', 'bison'), + '__cat': ('exe', 'required', 'cat'), + '__cc': ('exe', 'required', 'gcc'), + '__chgrp': ('exe', 'required', 'chgrp'), + '__chmod': ('exe', 'required', 'chmod'), + '__chown': ('exe', 'required', 'chown'), + '__cp': ('exe', 'required', 'cp'), + '__cvs': ('exe', 'required', 'cvs'), + '__cxx': ('exe', 'required', 'g++'), + '__flex': ('exe', 'required', 'flex'), + '__git': ('exe', 'required', 'git'), + '__grep': ('exe', 'required', 'grep'), + '__gzip': ('exe', 'required', 'gzip'), + '__id': ('exe', 'required', 'id'), + '__install': ('exe', 'required', 'install'), + '__install_info': ('exe', 'required', 'install-info'), + '__ld': ('exe', 'required', 'ld'), + '__ldconfig': ('exe', 'none', ''), + '__makeinfo': ('exe', 'required', 'makeinfo'), + '__mkdir': ('exe', 'required', 'mkdir'), + '__mv': ('exe', 'required', 'mv'), + '__nm': ('exe', 'required', 'nm'), + '__nm': ('exe', 'required', 'nm'), + '__objcopy': ('exe', 'required', 'objcopy'), + '__objdump': ('exe', 'required', 'objdump'), + '__patch': ('exe', 'required', 'patch'), + '__patch_bin': ('exe', 'required', 'patch'), + '__rm': ('exe', 'required', 'rm'), + '__sed': ('exe', 'required', 'sed'), + '__sh': ('exe', 'required', 'sh'), + '__tar': ('exe', 'required', 'bsdtar'), + '__touch': ('exe', 'required', 'touch'), + '__unzip': ('exe', 'required', 'unzip'), + '__xz': ('exe', 'required', 'xz'), + '_buildshell': ('exe', 'required', '%{__sh}'), + '___setup_shell': ('exe', 'required', '%{__sh}') + } + return defines + +if __name__ == '__main__': + pprint.pprint(load()) diff --git a/tester/.gitignore b/tester/.gitignore new file mode 100644 index 0000000..7e4a24c --- /dev/null +++ b/tester/.gitignore @@ -0,0 +1,4 @@ +*~ +*.pyc +*.log +log_* diff --git a/tester/config/base.cfg b/tester/config/base.cfg new file mode 100644 index 0000000..bb058d2 --- /dev/null +++ b/tester/config/base.cfg @@ -0,0 +1,35 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Base settings for RTEMS testing support. +# + +%define _target %{%{bsp_arch}-rtems%{version} diff --git a/tester/config/checks.cfg b/tester/config/checks.cfg new file mode 100644 index 0000000..b21d912 --- /dev/null +++ b/tester/config/checks.cfg @@ -0,0 +1,37 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Commdon checks for the RTEMS Tests. +# + +%if %{_target} == %{nil} + %error No 'target' defined +%endif diff --git a/tester/rt/__init__.py b/tester/rt/__init__.py new file mode 100644 index 0000000..c7eac97 --- /dev/null +++ b/tester/rt/__init__.py @@ -0,0 +1,30 @@ +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2013-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +all = ['test'] diff --git a/tester/rt/config.py b/tester/rt/config.py new file mode 100644 index 0000000..d426026 --- /dev/null +++ b/tester/rt/config.py @@ -0,0 +1,205 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2013-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# RTEMS Testing Config +# + +import datetime +import os +import threading + +from rtemstoolkit import config +from rtemstoolkit import error +from rtemstoolkit import execute +from rtemstoolkit import log +from rtemstoolkit import path + +import console +import gdb + +timeout = 15 + +class file(config.file): + """RTEMS Testing configuration.""" + + _directives = ['%execute', + '%gdb', + '%console'] + + def __init__(self, report, name, opts, _directives = _directives): + super(file, self).__init__(name, opts, directives = _directives) + self.lock = threading.Lock() + self.realtime_trace = self.debug_trace('output') + self.process = None + self.console = None + self.output = None + self.report = report + self.load(name) + + def __del__(self): + if self.console: + del self.console + super(file, self).__del__() + + def _lock(self): + self.lock.acquire() + + def _unlock(self): + self.lock.release() + + def _timeout(self): + self.capture('*** TIMEOUT TIMEOUT') + + def _dir_console(self, data): + if self.console is not None: + raise error.general(self._name_line_msg('console already configured')) + if len(data) == 0: + raise error.general(self._name_line_msg('no console configuration provided')) + console_trace = trace = self.debug_trace('console') + if data[0] == 'stdio': + self.console = console.stdio(trace = console_trace) + elif data[0] == 'tty': + if len(data) < 2 or len(data) >3: + raise error.general(self._name_line_msg('no tty configuration provided')) + if len(data) == 3: + settings = data[2] + else: + settings = None + self.console = console.tty(data[1], + output = self.capture, + setup = settings, + trace = console_trace) + else: + raise error.general(self._name_line_msg('invalid console type')) + + def _dir_execute(self, data, total, index, exe, bsp_arch, bsp): + self.process = execute.execute(output = self.capture) + if not self.in_error: + if self.console: + self.console.open() + self.capture_console('run: %s' % (' '.join(data))) + ec, proc = self.process.open(data, + timeout = (int(self.expand('%{timeout}')), + self._timeout)) + if ec > 0: + self._lock() + self._error('execute failed: %s: %s' % (' '.join(data), os.strerror(ec))) + self._unlock() + if self.console: + self.console.close() + + def _dir_gdb(self, data, total, index, exe, bsp_arch, bsp): + if len(data) < 3 or len(data) > 4: + raise error.general('invalid %gdb arguments') + self.process = gdb.gdb(bsp_arch, bsp, + trace = self.debug_trace('gdb'), + mi_trace = self.debug_trace('gdb-mi')) + script = self.expand('%%{%s}' % data[2]) + if script: + script = [l.strip() for l in script.splitlines()] + if not self.in_error: + if self.console: + self.console.open() + self.process.open(data[0], data[1], + script = script, + output = self.capture, + gdb_console = self.capture_console, + timeout = int(self.expand('%{timeout}'))) + if self.console: + self.console.close() + + def _directive_filter(self, results, directive, info, data): + if results[0] == 'directive': + _directive = results[1] + _data = results[2] + ds = [] + if len(_data): + ds = [_data[0]] + if len(_data) > 1: + ds += _data[1].split() + ds = self.expand(ds) + + if _directive == '%console': + self._dir_console(ds) + else: + self._lock() + try: + total = int(self.expand('%{test_total}')) + index = int(self.expand('%{test_index}')) + exe = self.expand('%{test_executable}') + bsp_arch = self.expand('%{bsp_arch}') + bsp = self.expand('%{bsp}') + self.report.start(index, total, exe, exe, bsp_arch, bsp) + self.output = [] + finally: + self._unlock() + if _directive == '%execute': + self._dir_execute(ds, total, index, exe, bsp_arch, bsp) + elif _directive == '%gdb': + self._dir_gdb(ds, total, index, exe, bsp_arch, bsp) + else: + raise error.general(self._name_line_msg('invalid directive')) + self._lock() + try: + self.report.end(exe, self.output) + self.process = None + self.output = None + finally: + self._unlock() + return None, None, None + + def _realtime_trace(self, text): + if self.realtime_trace: + for l in text: + print ' '.join(l) + + def capture(self, text): + text = [(']', l) for l in text.replace(chr(13), '').splitlines()] + self._lock() + if self.output is not None: + self._realtime_trace(text) + self.output += text + self._unlock() + + def capture_console(self, text): + text = [('>', l) for l in text.replace(chr(13), '').splitlines()] + self._lock() + if self.output is not None: + self._realtime_trace(text) + self.output += text + self._unlock() + + def debug_trace(self, flag): + dt = self.macros['debug_trace'] + if dt: + if flag in dt.split(','): + return True + return False diff --git a/tester/rt/console.py b/tester/rt/console.py new file mode 100644 index 0000000..74ec3bf --- /dev/null +++ b/tester/rt/console.py @@ -0,0 +1,143 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2013-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# RTEMS Testing Consoles +# + +import errno +import fcntl +import os +import threading +import time + +import stty + +def save(): + return stty.save() + +def restore(attributes): + stty.restore(attributes) + +class console(object): + '''RTEMS Testing console base.''' + + def __init__(self, name, trace): + self.name = name + self.trace = trace + + def __del__(self): + pass + + def _tracing(self): + return self.trace + + def open(self): + pass + + def close(self): + pass + +class stdio(console): + '''STDIO console.''' + + def __init__(self, trace = False): + super(stdio, self).__init__('stdio', trace) + +class tty(console): + '''TTY console connects to serial ports.''' + + raw = 'B115200,~BRKINT,IGNBRK,IGNCR,~ICANON,~ISIG,~IEXTEN,~ECHO,CLOCAL,~CRTSCTS' + + def __init__(self, dev, output, setup = None, trace = False): + self.tty = None + self.read_thread = None + self.dev = dev + self.output = output + if setup is None: + self.setup = raw + else: + self.setup = setup + super(tty, self).__init__(dev, trace) + + def __del__(self): + super(tty, self).__del__() + if self._tracing(): + print ':: tty close', self.dev + fcntl.fcntl(me.tty.fd, fcntl.F_SETFL, + fcntl.fcntl(me.tty.fd, fcntl.F_GETFL) & ~os.O_NONBLOCK) + self.close() + + def open(self): + def _readthread(me, x): + if self._tracing(): + print ':: tty runner started', self.dev + fcntl.fcntl(me.tty.fd, fcntl.F_SETFL, + fcntl.fcntl(me.tty.fd, fcntl.F_GETFL) | os.O_NONBLOCK) + line = '' + while me.running: + time.sleep(0.05) + try: + data = me.tty.fd.read() + except IOError, ioe: + if ioe.errno == errno.EAGAIN: + continue + raise + except: + raise + for c in data: + if len(c) == 0: + continue + if c != chr(0): + line += c + if c == '\n': + me.output(line) + line = '' + if self._tracing(): + print ':: tty runner finished', self.dev + if self._tracing(): + print ':: tty open', self.dev + self.tty = stty.tty(self.dev) + self.tty.set(self.setup) + self.tty.on() + self.read_thread = threading.Thread(target = _readthread, + name = 'tty[%s]' % (self.dev), + args = (self, 0)) + self.read_thread.daemon = True + self.running = True + self.read_thread.start() + + def close(self): + if self.tty: + time.sleep(1) + if self.read_thread: + self.running = False + self.read_thread.join(1) + self.tty = None diff --git a/tester/rt/gdb.py b/tester/rt/gdb.py new file mode 100644 index 0000000..4449222 --- /dev/null +++ b/tester/rt/gdb.py @@ -0,0 +1,321 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2013-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# RTEMS Testing GDB Interface +# + +import os +import Queue +import sys +import termios +import threading + +from rtemstoolkit import error +from rtemstoolkit import execute +from rtemstoolkit import options +from rtemstoolkit import path + +import console +import pygdb + +# +# The MI parser needs a global lock. It has global objects. +# +mi_lock = threading.Lock() + +class gdb(object): + '''RTEMS Testing GDB base.''' + + def __init__(self, bsp_arch, bsp, trace = False, mi_trace = False): + self.trace = trace + self.mi_trace = mi_trace + self.lock_trace = False + self.lock = threading.RLock() + self.script = None + self.script_line = 0 + self.bsp = bsp + self.bsp_arch = bsp_arch + self.output = None + self.gdb_console = None + self.input = Queue.Queue() + self.commands = Queue.Queue() + self.process = None + self.state = {} + self.running = False + self.breakpoints = {} + self.output = None + self.output_buffer = '' + self.lc = 0 + + def _lock(self, msg): + if self.lock_trace: + print '|[ LOCK:%s ]|' % (msg) + self.lock.acquire() + + def _unlock(self, msg): + if self.lock_trace: + print '|] UNLOCK:%s [|' % (msg) + self.lock.release() + + def _mi_lock(self): + mi_lock.acquire() + + def _mi_unlock(self): + mi_lock.release() + + def _put(self, text): + if self.trace: + print ')))', text + self.commands.put(text) + + def _input_commands(self): + if self.commands.empty(): + return False + try: + if self.trace: + print '... input empty ', self.input.empty() + if self.input.empty(): + line = self.commands.get(block = False) + if self.trace: + print '+++', line + self.input.put(line) + except: + pass + return True + + def _reader(self, line): + self._lock('_reader') + if self.trace: + print '<<<', line + try: + self.lc += 1 + if line.startswith('(gdb)'): + if self.trace: + print '^^^ (gdb)' + if not self._input_commands(): + self.gdb_expect() + self._input_commands() + else: + self.gdb_parse(line) + finally: + self._unlock('_reader') + + def _writer(self): + try: + try: + self._lock('_open') + try: + if self.process is None: + return None + finally: + self._unlock('_open') + line = self.input.get(timeout = 0.5) + if self.trace: + print '>>> input: queue=%d' % (self.input.qsize()), line + except Queue.Empty: + return True + if line is None: + return None + return line + os.linesep + except: + if self.trace: + print 'writer exception' + pass + if self.trace: + print 'writer closing' + return False + + def _timeout(self): + self._lock('_timeout') + try: + if self.output: + self.output('*** TIMEOUT TIMEOUT') + self._gdb_quit(backtrace = True) + finally: + self._unlock('_timeout') + + def _cleanup(self, proc): + self._lock('_cleanup') + try: + self._put(None) + finally: + self._unlock('_cleanup') + + def _gdb_quit(self, backtrace = False): + self._lock('_gdb_quit') + try: + self._put('-exec-interrupt') + if backtrace: + self._put('bt') + self._put('quit') + self._put('None') + if self.script: + self.script_line = len(self.script) + finally: + self._unlock('_gdb_quit') + + def open(self, command, executable, + output, gdb_console, script = None, tty = None, + timeout = 300): + self._lock('_open') + try: + cmds = execute.arg_list(command) + ['-i=mi', + '--nx', + '--quiet'] + if tty: + cmds += ['--tty=%s' % tty] + if executable: + cmds += [executable] + self.output = output + self.gdb_console = gdb_console + self.script = script + self.running = False + self.process = execute.execute(output = self._reader, + input = self._writer, + cleanup = self._cleanup) + finally: + self._unlock('_open') + try: + self.gdb_console('gdb: %s' % (' '.join(cmds))) + ec, proc = self.process.open(cmds, timeout = (timeout, self._timeout)) + if self.trace: + print 'gdb done', ec + if ec > 0: + raise error.general('gdb exec: %s' % (os.strerror(ec))) + except: + raise + self._lock('_open') + try: + self.process = None + finally: + self._unlock('_open') + + def gdb_expect(self): + if self.trace: + print '}}} gdb-expect' + if self.process and not self.running and self.script is not None: + if self.script_line == len(self.script): + self._put(None) + else: + if self.script_line == 0: + self._put('-gdb-set confirm no') + self._put('-data-list-register-names') + line = self.script[self.script_line] + self.script_line += 1 + self._put(line) + else: + self._put(line) + + def gdb_parse(self, lines): + try: + self._mi_lock() + try: + if self.mi_trace: + print 'mi-data:', lines + rec = pygdb.mi_parser.process(lines) + finally: + self._mi_unlock() + if self.mi_trace: + print 'mi-rec:', rec + if rec.record_type == 'result': + if rec.type == 'result': + if rec.class_ == 'error': + self._gdb_quit() + elif 'register_names' in dir(rec.result): + self.register_names = rec.result.register_names + elif 'register_values' in dir(rec.result): + self.register_values = rec.result.register_values + elif rec.type == 'exec': + if rec.class_ == 'running': + if self.trace: + print '*** running' + self._put('') + self.running = True + elif rec.class_ == 'stopped': + if self.trace: + print '*** stopped' + self.running = False + #self._put('-data-list-register-values') + elif rec.type == 'breakpoint': + if rec.class_ == 'breakpoint-created': + self.breakpoints[rec.result.bkpt.number] = rec.result.bkpt + elif rec.class_ == 'breakpoint-modified': + self.breakpoints[rec.result.bkpt.number] = rec.result.bkpt + elif rec.class_ == 'breakpoint-deleted': + if rec.result.id in self.breakpoints: + del self.breakpoints[rec.result.id] + elif rec.record_type == 'error': + self._gdb_quit() + elif rec.record_type == 'stream': + if rec.type == 'console' or rec.type == 'log': + for line in rec.value.splitlines(): + self.gdb_console(line) + if rec.type == 'target': + self.output_buffer += rec.value + last_lf = self.output_buffer.rfind('\n') + if last_lf >= 0: + lines = self.output_buffer[:last_lf] + if self.trace: + print '/// console output' + for line in lines.splitlines(): + self.output(line) + self.output_buffer = self.output_buffer[last_lf + 1:] + except: + if self.trace: + print '/// console output' + for line in lines.splitlines(): + self.output(line) + +if __name__ == "__main__": + stdtty = console.save() + try: + def output(text): + print ']', text + def gdb_console(text): + print '>', text + script = ['target sim'] + if len(sys.argv) > 1: + executable = sys.argv[1] + script += ['load', + 'run', + 'info reg', + '-stack-list-frames', + '-stack-list-arguments --all-values'] + else: + executable = None + script += ['quit'] + g = gdb('sparc', 'sis', mi_trace = True) + g.open('sparc-rtems4.11-gdb', executable, output, gdb_console, script) + except: + console.restore(stdtty) + raise + finally: + console.restore(stdtty) diff --git a/tester/rt/options.py b/tester/rt/options.py new file mode 100644 index 0000000..f637017 --- /dev/null +++ b/tester/rt/options.py @@ -0,0 +1,118 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2013-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Determine the defaults and load the specific file. +# + +import glob +import pprint +import re +import os +import string + +from rtemstoolkit import error +from rtemstoolkit import execute +from rtemstoolkit import git +from rtemstoolkit import log +from rtemstoolkit import macros +from rtemstoolkit import options +from rtemstoolkit import path + +import version + +class command_line(options.command_line): + """Process the command line in a common way for all Tool Builder commands.""" + + def __init__(self, argv = None, optargs = None, defaults = None, command_path = None): + if argv is None: + return + long_opts = { + # key macro handler param defs init + '--target' : ('_target', "triplet", True, None, False), + '--timeout' : ('timeout', "int", True, None, False), + } + long_opts_help = { + '--target': 'Set the target triplet', + '--timeout': 'Set the test timeout in seconds (default 180 seconds)' + } + super(command_line, self).__init__('rt', argv, optargs, defaults, + long_opts, long_opts_help, command_path); + + def __copy__(self): + return super(command_line, self).__copy__() + +def load(args, optargs = None, + command_path = None, + defaults = '%{_rtdir}/rtems/testing/defaults.mc'): + # + # The path to this command if not supplied by the upper layers. + # + if command_path is None: + command_path = path.dirname(args[0]) + if len(command_path) == 0: + command_path = '.' + # + # The command line contains the base defaults object all build objects copy + # and modify by loading a configuration. + # + opts = command_line(args, + optargs, + macros.macros(name = defaults, + rtdir = command_path), + command_path) + options.load(opts) + return opts + +def run(args): + try: + _opts = load(args = args, defaults = 'rtems/testing/defaults.mc') + log.notice('RTEMS Test - Defaults, v%s' % (version.str())) + _opts.log_info() + log.notice('Options:') + log.notice(str(_opts)) + log.notice('Defaults:') + log.notice(str(_opts.defaults)) + except error.general, gerr: + print gerr + sys.exit(1) + except error.internal, ierr: + print ierr + sys.exit(1) + except error.exit, eerr: + pass + except KeyboardInterrupt: + log.notice('abort: user terminated') + sys.exit(1) + sys.exit(0) + +if __name__ == '__main__': + import sys + run(sys.argv) diff --git a/tester/rt/pygdb/__init__.py b/tester/rt/pygdb/__init__.py new file mode 100644 index 0000000..b52f6f9 --- /dev/null +++ b/tester/rt/pygdb/__init__.py @@ -0,0 +1,21 @@ +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 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. + +all = ['mi_parser'] +from mi_parser import scan +from mi_parser import process diff --git a/tester/rt/pygdb/mi_parser.py b/tester/rt/pygdb/mi_parser.py new file mode 100755 index 0000000..65ea5e0 --- /dev/null +++ b/tester/rt/pygdb/mi_parser.py @@ -0,0 +1,423 @@ +#!/usr/bin/python + +# +# Copyright (c) 2008 Michael Eddington +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +# Authors: +# Frank Laub (frank.laub@gmail.com) +# Michael Eddington (mike@phed.org) + +# $Id$ + + +import re +import pprint +import spark + +def __private(): + class Token: + def __init__(self, type, value=None): + self.type = type + self.value = value + def __cmp__(self, o): + return cmp(self.type, o) + def __repr__(self): + return self.value or self.type + + class AST: + def __init__(self, type): + self.type = type + self._kids = [] + def __getitem__(self, i): + return self._kids[i] + def __len__(self): + return len(self._kids) + def __setslice__(self, low, high, seq): + self._kids[low:high] = seq + def __cmp__(self, o): + return cmp(self.type, o) + + class GdbMiScannerBase(spark.GenericScanner): + def tokenize(self, input): + self.rv = [] + spark.GenericScanner.tokenize(self, input) + return self.rv + + def t_nl(self, s): + r'\n|\r\n' + self.rv.append(Token('nl')) + + def t_whitespace(self, s): + r'[ \t\f\v]+' + pass + + def t_symbol(self, s): + r',|\{|\}|\[|\]|\=' + self.rv.append(Token(s, s)) + + def t_result_type(self, s): + r'\*|\+|\^' + self.rv.append(Token('result_type', s)) + + def t_stream_type(self, s): + r'\@|\&|\~' + self.rv.append(Token('stream_type', s)) + + def t_string(self, s): + r'[\w-]+' + self.rv.append(Token('string', s)) + + def t_c_string(self, s): + r'\".*?(?<![\\\\])\"' + inner = self.__unescape(s[1:len(s)-1]) + self.rv.append(Token('c_string', inner)) + + def t_default(self, s): + r'( . | \n )+' + raise Exception, "Specification error: unmatched input for '%s'" % s + + def __unescape(self, s): + s = re.sub(r'\\r', r'\r', s) + s = re.sub(r'\\n', r'\n', s) + s = re.sub(r'\\t', r'\t', s) + return re.sub(r'\\(.)', r'\1', s) + + + class GdbMiScanner(GdbMiScannerBase): + def t_token(self, s): + r'\d+' + self.rv.append(Token('token', s)) + + class GdbMiParser(spark.GenericASTBuilder): + def __init__(self): + spark.GenericASTBuilder.__init__(self, AST, 'output') + + def p_output(self, args): + ''' + output ::= record_list + record_list ::= generic_record + record_list ::= generic_record record_list + generic_record ::= result_record + generic_record ::= stream_record + result_record ::= result_header result_list nl + result_record ::= result_header nl + result_header ::= token result_type class + result_header ::= result_type class + result_header ::= token = class + result_header ::= = class + stream_record ::= stream_type c_string nl + result_list ::= , result result_list + result_list ::= , result + result_list ::= , tuple + result ::= variable = value + class ::= string + variable ::= string + value ::= const + value ::= tuple + value ::= list + value_list ::= , value + value_list ::= , value value_list + const ::= c_string + tuple ::= { } + tuple ::= { result } + tuple ::= { result result_list } + list ::= [ ] + list ::= [ value ] + list ::= [ value value_list ] + list ::= [ result ] + list ::= [ result result_list ] + list ::= { value } + list ::= { value value_list } + ''' + pass + + def terminal(self, token): + # Homogeneous AST. + rv = AST(token.type) + rv.value = token.value + return rv + + def nonterminal(self, type, args): + # Flatten AST a bit by not making nodes if there's only one child. + exclude = [ + 'record_list' + ] + if len(args) == 1 and type not in exclude: + return args[0] + return spark.GenericASTBuilder.nonterminal(self, type, args) + + def error(self, token, i=0, tokens=None): + if i > 2: + print '%s %s %s %s' % (tokens[i-3], tokens[i-2], tokens[i-1], tokens[i]) + raise Exception, "Syntax error at or near %d:'%s' token" % (i, token) + + class GdbMiInterpreter(spark.GenericASTTraversal): + def __init__(self, ast): + spark.GenericASTTraversal.__init__(self, ast) + self.postorder() + + def __translate_type(self, type): + table = { + '^': 'result', + '=': 'notify', + '+': 'status', + '*': 'exec', + '~': 'console', + '@': 'target', + '&': 'log' + } + return table[type] + + def n_result(self, node): + # result ::= variable = value + node.value = { node[0].value: node[2].value } + #print 'result: %s' % node.value + + def n_tuple(self, node): + if len(node) == 2: + # tuple ::= {} + node.value = {} + elif len(node) == 3: + # tuple ::= { result } + node.value = node[1].value + elif len(node) == 4: + # tuple ::= { result result_list } + node.value = node[1].value + for result in node[2].value: + for n, v in result.items(): + if node.value.has_key(n): + #print '**********list conversion: [%s] %s -> %s' % (n, node.value[n], v) + old = node.value[n] + if not isinstance(old, list): + node.value[n] = [ node.value[n] ] + node.value[n].append(v) + else: + node.value[n] = v + else: + raise Exception, 'Invalid tuple' + #print 'tuple: %s' % node.value + + def n_list(self, node): + if len(node) == 2: + # list ::= [] + node.value = [] + elif len(node) == 3: + # list ::= [ value ] + node.value = [ node[1].value ] + elif len(node) == 4: + # list ::= [ value value_list ] + node.value = [ node[1].value ] + node[2].value + #list ::= [ result ] + #list ::= [ result result_list ] + #list ::= { value } + #list ::= { value value_list } + #print 'list %s' % node.value + + def n_value_list(self, node): + if len(node) == 2: + #value_list ::= , value + node.value = [ node[1].value ] + elif len(node) == 3: + #value_list ::= , value value_list + node.value = [ node[1].value ] + node[2].value + + def n_result_list(self, node): + if len(node) == 2: + # result_list ::= , result + node.value = [ node[1].value ] + else: + # result_list ::= , result result_list + node.value = [ node[1].value ] + node[2].value + #print 'result_list: %s' % node.value + + def n_result_record(self, node): + node.value = node[0].value + if len(node) == 3: + # result_record ::= result_header result_list nl + node.value['results'] = node[1].value + elif len(node) == 2: + # result_record ::= result_header nl + pass + #print 'result_record: %s' % (node.value) + + def n_result_header(self, node): + if len(node) == 3: + # result_header ::= token result_type class + node.value = { + 'token': node[0].value, + 'type': self.__translate_type(node[1].value), + 'class_': node[2].value, + 'record_type': 'result' + } + elif len(node) == 2: + # result_header ::= result_type class + node.value = { + 'token': None, + 'type': self.__translate_type(node[0].value), + 'class_': node[1].value, + 'record_type': 'result' + } + + def n_stream_record(self, node): + # stream_record ::= stream_type c_string nl + node.value = { + 'type': self.__translate_type(node[0].value), + 'value': node[1].value, + 'record_type': 'stream' + } + #print 'stream_record: %s' % node.value + + def n_record_list(self, node): + if len(node) == 1: + # record_list ::= generic_record + node.value = [ node[0].value ] + elif len(node) == 2: + # record_list ::= generic_record record_list + node.value = [ node[0].value ] + node[1].value + #print 'record_list: %s' % node.value + + #def default(self, node): + #print 'default: ' + node.type + + class GdbDynamicObject: + def __init__(self, dict_): + self.graft(dict_) + + def __repr__(self): + return pprint.pformat(self.__dict__) + + def __nonzero__(self): + return len(self.__dict__) > 0 + + def __getitem__(self, i): + if i == 0 and len(self.__dict__) > 0: + return self + else: + raise IndexError + + def __getattr__(self, name): + if name.startswith('__'): + raise AttributeError + return None + + def graft(self, dict_): + for name, value in dict_.items(): + name = name.replace('-', '_') + if isinstance(value, dict): + value = GdbDynamicObject(value) + elif isinstance(value, list): + x = value + value = [] + for item in x: + if isinstance(item, dict): + item = GdbDynamicObject(item) + value.append(item) + setattr(self, name, value) + + class GdbMiRecord: + def __init__(self, record): + self.result = None + for name, value in record[0].items(): + name = name.replace('-', '_') + if name == 'results': + for result in value: + if not self.result: + self.result = GdbDynamicObject(result) + else: + # graft this result to self.results + self.result.graft(result) + else: + setattr(self, name, value) + + def __repr__(self): + return pprint.pformat(self.__dict__) + + return (GdbMiScanner(), GdbMiParser(), GdbMiInterpreter, GdbMiRecord) + +(__the_scanner, __the_parser, __the_interpreter, __the_output) = __private() + +def scan(input): + return __the_scanner.tokenize(input) + +def parse(tokens): + return __the_parser.parse(tokens) + +def process(input): + tokens = scan(input) + ast = parse(tokens) + __the_interpreter(ast) + return __the_output(ast.value) + +if __name__ == '__main__': + def main(): + def print_tokens(tokens): + print + for token in tokens: + if token.value: + print token.type + ': ' + token.value + else: + print token.type + + def run_test(test): + lines = test.splitlines() + for line in lines: + tokens = scan(line + '\n') + #print_tokens(tokens) + + ast = parse(tokens) + __the_interpreter(ast) + output = __the_output(ast.value) + print output + + x = '"No symbol table is loaded. Use the \\"file\\" command."' + m = re.match('\".*?(?<![\\\\])\"', x) + z = x[m.start():m.end()] + + test1 = '22^done,time={wallclock="0.05395",user="0.02996",system="0.02222",start="1210321030.972724",end="1210321031.026675"}\n' + test2 = '''~"[Switching to process 3832 local thread 0x3607]\\n" +=shlibs-updated +^running +''' + + test3 = '''=shlibs-added,shlib-info={num="2",name="qi",kind="-",dyld-addr="0x1000",reason="exec",requested-state="Y",state="Y",path="/Users/franklaub/bin/qi",description="/Users/franklaub/bin/qi",loaded_addr="",slide="0x0",prefix="",dsym-objpath="/Users/franklaub/bin/qi.dSYM/Contents/Resources/DWARF/qi"},time={now="1210290757.432413"} +=shlibs-added,shlib-info={num="3",name="libgcc_s.1.dylib",kind="-",dyld-addr="0x9230a000",reason="dyld",requested-state="Y",state="Y",path="/usr/lib/libgcc_s.1.dylib",description="/usr/lib/libgcc_s.1.dylib",loaded_addr="0x9230a000",slide="-0x6dcf6000",prefix=""},time={now="1210290757.432771"} +=shlibs-added,shlib-info={num="4",name="libSystem.B.dylib",kind="-",dyld-addr="0x950aa000",reason="dyld",requested-state="Y",state="Y",path="/usr/lib/libSystem.B.dylib",description="/usr/lib/libSystem.B.dylib",loaded_addr="0x950aa000",slide="-0x6af56000",prefix="",commpage-objpath="/usr/lib/libSystem.B.dylib[LC_SEGMENT.__DATA.__commpage]"},time={now="1210290757.433091"} +=shlibs-added,shlib-info={num="5",name="libmathCommon.A.dylib",kind="-",dyld-addr="0x95018000",reason="dyld",requested-state="Y",state="Y",path="/usr/lib/system/libmathCommon.A.dylib",description="/usr/lib/system/libmathCommon.A.dylib",loaded_addr="0x95018000",slide="-0x6afe8000",prefix=""},time={now="1210290757.433390"} +*stopped,time={wallclock="1.02740",user="0.00379",system="0.00791",start="1210290756.408774",end="1210290757.436179"},reason="breakpoint-hit",commands="no",times="1",bkptno="1",thread-id="1" +''' + + test4 = '''=shlibs-added,shlib-info={num="2",name="qi",kind="-",dyld-addr="0x1000",reason="exec",requested-state="Y",state="Y",path="/Users/franklaub/bin/qi",description="/Users/franklaub/bin/qi",loaded_addr="",slide="0x0",prefix="",dsym-objpath="/Users/franklaub/bin/qi.dSYM/Contents/Resources/DWARF/qi"},time={now="1210290757.432413"} +^running +''' + test5 = '''=class,variable={frame={x="2"},frame={x="2"}, regs={"1","2","3"}} +^running +''' + test6 = '10^done,stack-args={frame={level="0",args={}}},time={wallclock="0.00006",user="0.00004",system="0.00002",start="1210530442.460765",end="1210530442.460825"}\n' + + run_test(test1) + run_test(test2) + run_test(test3) + run_test(test4) + run_test(test5) + run_test(test6) + + main() diff --git a/tester/rt/pygdb/spark.py b/tester/rt/pygdb/spark.py new file mode 100644 index 0000000..aab2d19 --- /dev/null +++ b/tester/rt/pygdb/spark.py @@ -0,0 +1,847 @@ +# Copyright (c) 1998-2002 John Aycock +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +__version__ = 'SPARK-0.7 (pre-alpha-7)' + +import re +import sys +import string + +def _namelist(instance): + namelist, namedict, classlist = [], {}, [instance.__class__] + for c in classlist: + for b in c.__bases__: + classlist.append(b) + for name in c.__dict__.keys(): + if not namedict.has_key(name): + namelist.append(name) + namedict[name] = 1 + return namelist + +class GenericScanner: + def __init__(self, flags=0): + pattern = self.reflect() + self.re = re.compile(pattern, re.VERBOSE|flags) + + self.index2func = {} + for name, number in self.re.groupindex.items(): + self.index2func[number-1] = getattr(self, 't_' + name) + + def makeRE(self, name): + doc = getattr(self, name).__doc__ + rv = '(?P<%s>%s)' % (name[2:], doc) + return rv + + def reflect(self): + rv = [] + for name in _namelist(self): + if name[:2] == 't_' and name != 't_default': + rv.append(self.makeRE(name)) + + rv.append(self.makeRE('t_default')) + return string.join(rv, '|') + + def error(self, s, pos): + print "Lexical error at position %s" % pos + raise SystemExit + + def position(self, newpos=None): + oldpos = self.pos + if newpos is not None: + self.pos = newpos + return self.string, oldpos + + def tokenize(self, s): + self.string = s + self.pos = 0 + n = len(s) + while self.pos < n: + m = self.re.match(s, self.pos) + if m is None: + self.error(s, self.pos) + + groups = m.groups() + self.pos = m.end() + for i in range(len(groups)): + if groups[i] is not None and self.index2func.has_key(i): + self.index2func[i](groups[i]) + + def t_default(self, s): + r'( . | \n )+' + print "Specification error: unmatched input" + raise SystemExit + +# +# Extracted from GenericParser and made global so that [un]picking works. +# +class _State: + def __init__(self, stateno, items): + self.T, self.complete, self.items = [], [], items + self.stateno = stateno + +class GenericParser: + # + # An Earley parser, as per J. Earley, "An Efficient Context-Free + # Parsing Algorithm", CACM 13(2), pp. 94-102. Also J. C. Earley, + # "An Efficient Context-Free Parsing Algorithm", Ph.D. thesis, + # Carnegie-Mellon University, August 1968. New formulation of + # the parser according to J. Aycock, "Practical Earley Parsing + # and the SPARK Toolkit", Ph.D. thesis, University of Victoria, + # 2001, and J. Aycock and R. N. Horspool, "Practical Earley + # Parsing", unpublished paper, 2001. + # + + def __init__(self, start): + self.rules = {} + self.rule2func = {} + self.rule2name = {} + self.collectRules() + self.augment(start) + self.ruleschanged = 1 + + _NULLABLE = '\e_' + _START = 'START' + _BOF = '|-' + + # + # When pickling, take the time to generate the full state machine; + # some information is then extraneous, too. Unfortunately we + # can't save the rule2func map. + # + def __getstate__(self): + if self.ruleschanged: + # + # XXX - duplicated from parse() + # + self.computeNull() + self.newrules = {} + self.new2old = {} + self.makeNewRules() + self.ruleschanged = 0 + self.edges, self.cores = {}, {} + self.states = { 0: self.makeState0() } + self.makeState(0, self._BOF) + # + # XXX - should find a better way to do this.. + # + changes = 1 + while changes: + changes = 0 + for k, v in self.edges.items(): + if v is None: + state, sym = k + if self.states.has_key(state): + self.goto(state, sym) + changes = 1 + rv = self.__dict__.copy() + for s in self.states.values(): + del s.items + del rv['rule2func'] + del rv['nullable'] + del rv['cores'] + return rv + + def __setstate__(self, D): + self.rules = {} + self.rule2func = {} + self.rule2name = {} + self.collectRules() + start = D['rules'][self._START][0][1][1] # Blech. + self.augment(start) + D['rule2func'] = self.rule2func + D['makeSet'] = self.makeSet_fast + self.__dict__ = D + + # + # A hook for GenericASTBuilder and GenericASTMatcher. Mess + # thee not with this; nor shall thee toucheth the _preprocess + # argument to addRule. + # + def preprocess(self, rule, func): return rule, func + + def addRule(self, doc, func, _preprocess=1): + fn = func + rules = string.split(doc) + + index = [] + for i in range(len(rules)): + if rules[i] == '::=': + index.append(i-1) + index.append(len(rules)) + + for i in range(len(index)-1): + lhs = rules[index[i]] + rhs = rules[index[i]+2:index[i+1]] + rule = (lhs, tuple(rhs)) + + if _preprocess: + rule, fn = self.preprocess(rule, func) + + if self.rules.has_key(lhs): + self.rules[lhs].append(rule) + else: + self.rules[lhs] = [ rule ] + self.rule2func[rule] = fn + self.rule2name[rule] = func.__name__[2:] + self.ruleschanged = 1 + + def collectRules(self): + for name in _namelist(self): + if name[:2] == 'p_': + func = getattr(self, name) + doc = func.__doc__ + self.addRule(doc, func) + + def augment(self, start): + rule = '%s ::= %s %s' % (self._START, self._BOF, start) + self.addRule(rule, lambda args: args[1], 0) + + def computeNull(self): + self.nullable = {} + tbd = [] + + for rulelist in self.rules.values(): + lhs = rulelist[0][0] + self.nullable[lhs] = 0 + for rule in rulelist: + rhs = rule[1] + if len(rhs) == 0: + self.nullable[lhs] = 1 + continue + # + # We only need to consider rules which + # consist entirely of nonterminal symbols. + # This should be a savings on typical + # grammars. + # + for sym in rhs: + if not self.rules.has_key(sym): + break + else: + tbd.append(rule) + changes = 1 + while changes: + changes = 0 + for lhs, rhs in tbd: + if self.nullable[lhs]: + continue + for sym in rhs: + if not self.nullable[sym]: + break + else: + self.nullable[lhs] = 1 + changes = 1 + + def makeState0(self): + s0 = _State(0, []) + for rule in self.newrules[self._START]: + s0.items.append((rule, 0)) + return s0 + + def finalState(self, tokens): + # + # Yuck. + # + if len(self.newrules[self._START]) == 2 and len(tokens) == 0: + return 1 + start = self.rules[self._START][0][1][1] + return self.goto(1, start) + + def makeNewRules(self): + worklist = [] + for rulelist in self.rules.values(): + for rule in rulelist: + worklist.append((rule, 0, 1, rule)) + + for rule, i, candidate, oldrule in worklist: + lhs, rhs = rule + n = len(rhs) + while i < n: + sym = rhs[i] + if not self.rules.has_key(sym) or \ + not self.nullable[sym]: + candidate = 0 + i = i + 1 + continue + + newrhs = list(rhs) + newrhs[i] = self._NULLABLE+sym + newrule = (lhs, tuple(newrhs)) + worklist.append((newrule, i+1, + candidate, oldrule)) + candidate = 0 + i = i + 1 + else: + if candidate: + lhs = self._NULLABLE+lhs + rule = (lhs, rhs) + if self.newrules.has_key(lhs): + self.newrules[lhs].append(rule) + else: + self.newrules[lhs] = [ rule ] + self.new2old[rule] = oldrule + + def typestring(self, token): + return None + + def error(self, token): + print "Syntax error at or near `%s' token" % token + raise SystemExit + + def parse(self, tokens): + sets = [ [(1,0), (2,0)] ] + self.links = {} + + if self.ruleschanged: + self.computeNull() + self.newrules = {} + self.new2old = {} + self.makeNewRules() + self.ruleschanged = 0 + self.edges, self.cores = {}, {} + self.states = { 0: self.makeState0() } + self.makeState(0, self._BOF) + + for i in xrange(len(tokens)): + sets.append([]) + + if sets[i] == []: + break + self.makeSet(tokens[i], sets, i) + else: + sets.append([]) + self.makeSet(None, sets, len(tokens)) + + #_dump(tokens, sets, self.states) + + finalitem = (self.finalState(tokens), 0) + if finalitem not in sets[-2]: + if len(tokens) > 0: + self.error(tokens[i-1], i, tokens) + else: + self.error(None) + + return self.buildTree(self._START, finalitem, + tokens, len(sets)-2) + + def isnullable(self, sym): + # + # For symbols in G_e only. If we weren't supporting 1.5, + # could just use sym.startswith(). + # + return self._NULLABLE == sym[0:len(self._NULLABLE)] + + def skip(self, (lhs, rhs), pos=0): + n = len(rhs) + while pos < n: + if not self.isnullable(rhs[pos]): + break + pos = pos + 1 + return pos + + def makeState(self, state, sym): + assert sym is not None + # + # Compute \epsilon-kernel state's core and see if + # it exists already. + # + kitems = [] + for rule, pos in self.states[state].items: + lhs, rhs = rule + if rhs[pos:pos+1] == (sym,): + kitems.append((rule, self.skip(rule, pos+1))) + core = kitems + + core.sort() + tcore = tuple(core) + if self.cores.has_key(tcore): + return self.cores[tcore] + # + # Nope, doesn't exist. Compute it and the associated + # \epsilon-nonkernel state together; we'll need it right away. + # + k = self.cores[tcore] = len(self.states) + K, NK = _State(k, kitems), _State(k+1, []) + self.states[k] = K + predicted = {} + + edges = self.edges + rules = self.newrules + for X in K, NK: + worklist = X.items + for item in worklist: + rule, pos = item + lhs, rhs = rule + if pos == len(rhs): + X.complete.append(rule) + continue + + nextSym = rhs[pos] + key = (X.stateno, nextSym) + if not rules.has_key(nextSym): + if not edges.has_key(key): + edges[key] = None + X.T.append(nextSym) + else: + edges[key] = None + if not predicted.has_key(nextSym): + predicted[nextSym] = 1 + for prule in rules[nextSym]: + ppos = self.skip(prule) + new = (prule, ppos) + NK.items.append(new) + # + # Problem: we know K needs generating, but we + # don't yet know about NK. Can't commit anything + # regarding NK to self.edges until we're sure. Should + # we delay committing on both K and NK to avoid this + # hacky code? This creates other problems.. + # + if X is K: + edges = {} + + if NK.items == []: + return k + + # + # Check for \epsilon-nonkernel's core. Unfortunately we + # need to know the entire set of predicted nonterminals + # to do this without accidentally duplicating states. + # + core = predicted.keys() + core.sort() + tcore = tuple(core) + if self.cores.has_key(tcore): + self.edges[(k, None)] = self.cores[tcore] + return k + + nk = self.cores[tcore] = self.edges[(k, None)] = NK.stateno + self.edges.update(edges) + self.states[nk] = NK + return k + + def goto(self, state, sym): + key = (state, sym) + if not self.edges.has_key(key): + # + # No transitions from state on sym. + # + return None + + rv = self.edges[key] + if rv is None: + # + # Target state isn't generated yet. Remedy this. + # + rv = self.makeState(state, sym) + self.edges[key] = rv + return rv + + def gotoT(self, state, t): + return [self.goto(state, t)] + + def gotoST(self, state, st): + rv = [] + for t in self.states[state].T: + if st == t: + rv.append(self.goto(state, t)) + return rv + + def add(self, set, item, i=None, predecessor=None, causal=None): + if predecessor is None: + if item not in set: + set.append(item) + else: + key = (item, i) + if item not in set: + self.links[key] = [] + set.append(item) + self.links[key].append((predecessor, causal)) + + def makeSet(self, token, sets, i): + cur, next = sets[i], sets[i+1] + + ttype = token is not None and self.typestring(token) or None + if ttype is not None: + fn, arg = self.gotoT, ttype + else: + fn, arg = self.gotoST, token + + for item in cur: + ptr = (item, i) + state, parent = item + add = fn(state, arg) + for k in add: + if k is not None: + self.add(next, (k, parent), i+1, ptr) + nk = self.goto(k, None) + if nk is not None: + self.add(next, (nk, i+1)) + + if parent == i: + continue + + for rule in self.states[state].complete: + lhs, rhs = rule + for pitem in sets[parent]: + pstate, pparent = pitem + k = self.goto(pstate, lhs) + if k is not None: + why = (item, i, rule) + pptr = (pitem, parent) + self.add(cur, (k, pparent), + i, pptr, why) + nk = self.goto(k, None) + if nk is not None: + self.add(cur, (nk, i)) + + def makeSet_fast(self, token, sets, i): + # + # Call *only* when the entire state machine has been built! + # It relies on self.edges being filled in completely, and + # then duplicates and inlines code to boost speed at the + # cost of extreme ugliness. + # + cur, next = sets[i], sets[i+1] + ttype = token is not None and self.typestring(token) or None + + for item in cur: + ptr = (item, i) + state, parent = item + if ttype is not None: + k = self.edges.get((state, ttype), None) + if k is not None: + #self.add(next, (k, parent), i+1, ptr) + #INLINED --v + new = (k, parent) + key = (new, i+1) + if new not in next: + self.links[key] = [] + next.append(new) + self.links[key].append((ptr, None)) + #INLINED --^ + #nk = self.goto(k, None) + nk = self.edges.get((k, None), None) + if nk is not None: + #self.add(next, (nk, i+1)) + #INLINED --v + new = (nk, i+1) + if new not in next: + next.append(new) + #INLINED --^ + else: + add = self.gotoST(state, token) + for k in add: + if k is not None: + self.add(next, (k, parent), i+1, ptr) + #nk = self.goto(k, None) + nk = self.edges.get((k, None), None) + if nk is not None: + self.add(next, (nk, i+1)) + + if parent == i: + continue + + for rule in self.states[state].complete: + lhs, rhs = rule + for pitem in sets[parent]: + pstate, pparent = pitem + #k = self.goto(pstate, lhs) + k = self.edges.get((pstate, lhs), None) + if k is not None: + why = (item, i, rule) + pptr = (pitem, parent) + #self.add(cur, (k, pparent), + # i, pptr, why) + #INLINED --v + new = (k, pparent) + key = (new, i) + if new not in cur: + self.links[key] = [] + cur.append(new) + self.links[key].append((pptr, why)) + #INLINED --^ + #nk = self.goto(k, None) + nk = self.edges.get((k, None), None) + if nk is not None: + #self.add(cur, (nk, i)) + #INLINED --v + new = (nk, i) + if new not in cur: + cur.append(new) + #INLINED --^ + + def predecessor(self, key, causal): + for p, c in self.links[key]: + if c == causal: + return p + assert 0 + + def causal(self, key): + links = self.links[key] + if len(links) == 1: + return links[0][1] + choices = [] + rule2cause = {} + for p, c in links: + rule = c[2] + choices.append(rule) + rule2cause[rule] = c + return rule2cause[self.ambiguity(choices)] + + def deriveEpsilon(self, nt): + if len(self.newrules[nt]) > 1: + rule = self.ambiguity(self.newrules[nt]) + else: + rule = self.newrules[nt][0] + #print rule + + rhs = rule[1] + attr = [None] * len(rhs) + + for i in range(len(rhs)-1, -1, -1): + attr[i] = self.deriveEpsilon(rhs[i]) + return self.rule2func[self.new2old[rule]](attr) + + def buildTree(self, nt, item, tokens, k): + state, parent = item + + choices = [] + for rule in self.states[state].complete: + if rule[0] == nt: + choices.append(rule) + rule = choices[0] + if len(choices) > 1: + rule = self.ambiguity(choices) + #print rule + + rhs = rule[1] + attr = [None] * len(rhs) + + for i in range(len(rhs)-1, -1, -1): + sym = rhs[i] + if not self.newrules.has_key(sym): + if sym != self._BOF: + attr[i] = tokens[k-1] + key = (item, k) + item, k = self.predecessor(key, None) + #elif self.isnullable(sym): + elif self._NULLABLE == sym[0:len(self._NULLABLE)]: + attr[i] = self.deriveEpsilon(sym) + else: + key = (item, k) + why = self.causal(key) + attr[i] = self.buildTree(sym, why[0], + tokens, why[1]) + item, k = self.predecessor(key, why) + return self.rule2func[self.new2old[rule]](attr) + + def ambiguity(self, rules): + # + # XXX - problem here and in collectRules() if the same rule + # appears in >1 method. Also undefined results if rules + # causing the ambiguity appear in the same method. + # + sortlist = [] + name2index = {} + for i in range(len(rules)): + lhs, rhs = rule = rules[i] + name = self.rule2name[self.new2old[rule]] + sortlist.append((len(rhs), name)) + name2index[name] = i + sortlist.sort() + list = map(lambda (a,b): b, sortlist) + return rules[name2index[self.resolve(list)]] + + def resolve(self, list): + # + # Resolve ambiguity in favor of the shortest RHS. + # Since we walk the tree from the top down, this + # should effectively resolve in favor of a "shift". + # + return list[0] + +# +# GenericASTBuilder automagically constructs a concrete/abstract syntax tree +# for a given input. The extra argument is a class (not an instance!) +# which supports the "__setslice__" and "__len__" methods. +# +# XXX - silently overrides any user code in methods. +# + +class GenericASTBuilder(GenericParser): + def __init__(self, AST, start): + GenericParser.__init__(self, start) + self.AST = AST + + def preprocess(self, rule, func): + rebind = lambda lhs, self=self: \ + lambda args, lhs=lhs, self=self: \ + self.buildASTNode(args, lhs) + lhs, rhs = rule + return rule, rebind(lhs) + + def buildASTNode(self, args, lhs): + children = [] + for arg in args: + if isinstance(arg, self.AST): + children.append(arg) + else: + children.append(self.terminal(arg)) + return self.nonterminal(lhs, children) + + def terminal(self, token): return token + + def nonterminal(self, type, args): + rv = self.AST(type) + rv[:len(args)] = args + return rv + +# +# GenericASTTraversal is a Visitor pattern according to Design Patterns. For +# each node it attempts to invoke the method n_<node type>, falling +# back onto the default() method if the n_* can't be found. The preorder +# traversal also looks for an exit hook named n_<node type>_exit (no default +# routine is called if it's not found). To prematurely halt traversal +# of a subtree, call the prune() method -- this only makes sense for a +# preorder traversal. Node type is determined via the typestring() method. +# + +class GenericASTTraversalPruningException: + pass + +class GenericASTTraversal: + def __init__(self, ast): + self.ast = ast + + def typestring(self, node): + return node.type + + def prune(self): + raise GenericASTTraversalPruningException + + def preorder(self, node=None): + if node is None: + node = self.ast + + try: + name = 'n_' + self.typestring(node) + if hasattr(self, name): + func = getattr(self, name) + func(node) + else: + self.default(node) + except GenericASTTraversalPruningException: + return + + for kid in node: + self.preorder(kid) + + name = name + '_exit' + if hasattr(self, name): + func = getattr(self, name) + func(node) + + def postorder(self, node=None): + if node is None: + node = self.ast + + for kid in node: + self.postorder(kid) + + name = 'n_' + self.typestring(node) + if hasattr(self, name): + func = getattr(self, name) + func(node) + else: + self.default(node) + + + def default(self, node): + pass + +# +# GenericASTMatcher. AST nodes must have "__getitem__" and "__cmp__" +# implemented. +# +# XXX - makes assumptions about how GenericParser walks the parse tree. +# + +class GenericASTMatcher(GenericParser): + def __init__(self, start, ast): + GenericParser.__init__(self, start) + self.ast = ast + + def preprocess(self, rule, func): + rebind = lambda func, self=self: \ + lambda args, func=func, self=self: \ + self.foundMatch(args, func) + lhs, rhs = rule + rhslist = list(rhs) + rhslist.reverse() + + return (lhs, tuple(rhslist)), rebind(func) + + def foundMatch(self, args, func): + func(args[-1]) + return args[-1] + + def match_r(self, node): + self.input.insert(0, node) + children = 0 + + for child in node: + if children == 0: + self.input.insert(0, '(') + children = children + 1 + self.match_r(child) + + if children > 0: + self.input.insert(0, ')') + + def match(self, ast=None): + if ast is None: + ast = self.ast + self.input = [] + + self.match_r(ast) + self.parse(self.input) + + def resolve(self, list): + # + # Resolve ambiguity in favor of the longest RHS. + # + return list[-1] + +def _dump(tokens, sets, states): + for i in range(len(sets)): + print 'set', i + for item in sets[i]: + print '\t', item + for (lhs, rhs), pos in states[item[0]].items: + print '\t\t', lhs, '::=', + print string.join(rhs[:pos]), + print '.', + print string.join(rhs[pos:]) + if i < len(tokens): + print + print 'token', str(tokens[i]) + print diff --git a/tester/rt/report.py b/tester/rt/report.py new file mode 100644 index 0000000..1aa1f35 --- /dev/null +++ b/tester/rt/report.py @@ -0,0 +1,185 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2013-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# RTEMS Testing Reports +# + +import datetime +import os +import threading + +from rtemstoolkit import error +from rtemstoolkit import log +from rtemstoolkit import path + +class report(object): + '''RTEMS Testing report.''' + + def __init__(self, total): + self.lock = threading.Lock() + self.total = total + self.total_len = len(str(total)) + self.passed = 0 + self.failed = 0 + self.timeouts = 0 + self.invalids = 0 + self.invalid_tests = 0 + self.results = {} + self.name_max_len = 0 + + def __str__(self): + msg = 'Passed: %*d%s' % (self.total_len, self.passed, os.linesep) + msg += 'Failed: %*d%s' % (self.total_len, self.failed, os.linesep) + msg += 'Timeouts: %*d%s' % (self.total_len, self.timeouts, os.linesep) + msg += 'Invalid: %*d%s' % (self.total_len, self.invalids, os.linesep) + return msg + + def set_invalid_tests(self, invalid_tests): + self.invalid_tests = invalid_tests + + def start(self, index, total, name, executable, bsp_arch, bsp): + header = '[%*d/%*d] p:%-*d f:%-*d t:%-*d i:%-*d | %s/%s: %s' % \ + (len(str(total)), index, + len(str(total)), total, + len(str(total)), self.passed, + len(str(total)), self.failed, + len(str(total)), self.timeouts, + len(str(total)), self.invalids, + bsp_arch, + bsp, + path.basename(executable)) + self.lock.acquire() + if name in self.results: + self.lock.release() + raise error.general('duplicate test: %s' % (name)) + self.results[name] = { 'index': index, + 'bsp': bsp, + 'bsp_arch': bsp_arch, + 'exe': executable, + 'start': datetime.datetime.now(), + 'end': None, + 'result': None, + 'output': None, + 'header': header } + + self.lock.release() + log.notice(header, stdout_only = True) + + def end(self, name, output): + start = False + end = False + timeout = False + prefixed_output = [] + for line in output: + if line[0] == ']': + if line[1].startswith('*** '): + if line[1][4:].startswith('END OF '): + end = True + if line[1][4:].startswith('TIMEOUT TIMEOUT'): + timeout = True + else: + start = True + prefixed_output += [line[0] + ' ' + line[1]] + self.lock.acquire() + if name not in self.results: + self.lock.release() + raise error.general('test report missing: %s' % (name)) + if self.results[name]['end'] is not None: + self.lock.release() + raise error.general('test already finished: %s' % (name)) + self.results[name]['end'] = datetime.datetime.now() + if start and end: + status = 'passed' + self.passed += 1 + elif timeout: + status = 'timeout' + self.timeouts += 1 + elif start: + if not end: + status = 'failed' + self.failed += 1 + else: + if self.invalid_tests and path.basename(name) in self.invalid_tests: + status = 'passed' + self.passed += 1 + else: + status = 'invalid' + self.invalids += 1 + self.results[name]['result'] = status + self.results[name]['output'] = prefixed_output + if self.name_max_len < len(path.basename(name)): + self.name_max_len = len(path.basename(name)) + self.lock.release() + + def log(self, name, mode): + if mode != 'none': + self.lock.acquire() + if name not in self.results: + self.lock.release() + raise error.general('test report missing: %s' % (name)) + result = self.results[name]['result'] + time = self.results[name]['end'] - self.results[name]['start'] + if mode != 'none': + header = self.results[name]['header'] + if mode == 'all' or result != 'passed': + output = self.results[name]['output'] + else: + output = None + self.lock.release() + if header: + log.output(header) + if output: + log.output(output) + if header: + log.output('Result: %-10s Time: %s' % (result, str(time))) + + def summary(self): + def show_state(results, state, max_len): + for name in results: + if results[name]['result'] == state: + log.output(' %s' % (path.basename(name))) + log.output() + log.notice('Passed: %*d' % (self.total_len, self.passed)) + log.notice('Failed: %*d' % (self.total_len, self.failed)) + log.notice('Timeouts: %*d' % (self.total_len, self.timeouts)) + log.notice('Invalid: %*d' % (self.total_len, self.invalids)) + log.output('----------%s' % ('-' * self.total_len)) + log.notice('Total: %*d' % (self.total_len, self.total)) + log.output() + if self.failed: + log.output('Failures:') + show_state(self.results, 'failed', self.name_max_len) + if self.timeouts: + log.output('Timeouts:') + show_state(self.results, 'timeout', self.name_max_len) + if self.invalids: + log.output('Invalid:') + show_state(self.results, 'invalid', self.name_max_len) diff --git a/tester/rt/stty.py b/tester/rt/stty.py new file mode 100644 index 0000000..d059b40 --- /dev/null +++ b/tester/rt/stty.py @@ -0,0 +1,567 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2013-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# RTEMS Testing Consoles +# + +import os +import sys +import termios + +from rtemstoolkit import error +from rtemstoolkit import options +from rtemstoolkit import path + +def save(): + if not options.host_windows: + try: + sin = termios.tcgetattr(sys.stdin) + sout = termios.tcgetattr(sys.stdout) + serr = termios.tcgetattr(sys.stderr) + return sin, sout, serr + except: + pass + return None + +def restore(attributes): + if attributes is not None: + termios.tcsetattr(sys.stdin, termios.TCSANOW, attributes[0]) + termios.tcsetattr(sys.stdout, termios.TCSANOW, attributes[1]) + termios.tcsetattr(sys.stderr, termios.TCSANOW, attributes[2]) + +class tty: + + def __init__(self, dev): + if options.host_windows: + raise error.general('termios not support on host') + self.dev = dev + self.default_attr = None + self.fd = None + self.if_on = False + if options.host_windows: + raise error.general('TTY consoles not supported on Windows.') + if not path.exists(dev): + raise error.general('dev not found: %s' % (dev)) + try: + self.fd = open(dev, 'rw') + except IOError, ioe: + raise error.general('opening tty dev: %s: %s' % (dev, ioe)) + except: + raise error.general('opening tty dev: %s: unknown' % (dev)) + try: + self.default_attr = termios.tcgetattr(self.fd) + except: + raise error.general('cannot get termios attrs: %s' % (dev)) + self.attr = self.default_attr + + def __del__(self): + if self.fd and self.default_attr: + try: + self.fd.close() + except: + pass + + def __str__(self): + def _input(attr): + s = '' + if attr & termios.IGNBRK: + s += ' IGNBRK' + if attr & termios.BRKINT: + s += ' BRKINT' + if attr & termios.IGNPAR: + s += ' IGNPAR' + if attr & termios.PARMRK: + s += ' PARMRK' + if attr & termios.INPCK: + s += ' INPCK' + if attr & termios.ISTRIP: + s += ' ISTRIP' + if attr & termios.INLCR: + s += ' INLCR' + if attr & termios.IGNCR: + s += ' IGNCR' + if attr & termios.IXON: + s += ' IXON' + if attr & termios.IXOFF: + s += ' IXOFF' + if attr & termios.IXANY: + s += ' IXANY' + if attr & termios.IMAXBEL: + s += ' IMAXBEL' + return s + + def _output(attr): + s = '' + if attr & termios.OPOST: + s += ' OPOST' + if attr & termios.ONLCR: + s += ' ONLCR' + if attr & termios.OCRNL: + s += ' OCRNL' + if attr & termios.TABDLY: + s += ' TABDLY' + if attr & termios.TAB0: + s += ' TAB0' + if attr & termios.TAB3: + s += ' TAB3' + if attr & termios.ONOCR: + s += ' ONOCR' + if attr & termios.ONLRET: + s += ' ONLRET' + return s + + def _control(attr): + s = '' + if (attr & termios.CSIZE) == termios.CS5: + s += ' CS5' + if (attr & termios.CSIZE) == termios.CS6: + s += ' CS6' + if (attr & termios.CSIZE) == termios.CS7: + s += ' CS7' + if (attr & termios.CSIZE) == termios.CS8: + s += ' CS8' + if attr & termios.CSTOPB: + s += ' CSTOPB' + if attr & termios.CREAD: + s += ' CREAD' + if attr & termios.PARENB: + s += ' PARENB' + if attr & termios.PARODD: + s += ' PARODD' + if attr & termios.HUPCL: + s += ' HUPCL' + if attr & termios.CLOCAL: + s += ' CLOCAL' + if attr & termios.CRTSCTS: + s += ' CRTSCTS' + return s + + def _local(attr): + s = '' + if attr & termios.ECHOKE: + s += ' ECHOKE' + if attr & termios.ECHOE: + s += ' ECHOE' + if attr & termios.ECHO: + s += ' ECHO' + if attr & termios.ECHONL: + s += ' ECHONL' + if attr & termios.ECHOPRT: + s += ' ECHOPRT' + if attr & termios.ECHOCTL: + s += ' ECHOCTL' + if attr & termios.ISIG: + s += ' ISIG' + if attr & termios.ICANON: + s += ' ICANON' + if attr & termios.IEXTEN: + s += ' IEXTEN' + if attr & termios.TOSTOP: + s += ' TOSTOP' + if attr & termios.FLUSHO: + s += ' FLUSHO' + if attr & termios.PENDIN: + s += ' PENDIN' + if attr & termios.NOFLSH: + s += ' NOFLSH' + return s + + def _baudrate(attr): + if attr == termios.B0: + s = 'B0' + elif attr == termios.B50: + s = 'B50' + elif attr == termios.B75: + s = 'B75' + elif attr == termios.B110: + s = 'B110' + elif attr == termios.B134: + s = 'B134' + elif attr == termios.B150: + s = 'B150' + elif attr == termios.B200: + s = 'B200' + elif attr == termios.B300: + s = 'B300' + elif attr == termios.B600: + s = 'B600' + elif attr == termios.B1800: + s = 'B1800' + elif attr == termios.B1200: + s = 'B1200' + elif attr == termios.B2400: + s = 'B2400' + elif attr == termios.B4800: + s = 'B4800' + elif attr == termios.B9600: + s = 'B9600' + elif attr == termios.B19200: + s = 'B19200' + elif attr == termios.B38400: + s = 'B38400' + elif attr == termios.B57600: + s = 'B57600' + elif attr == termios.B115200: + s = 'B115200' + elif attr == termios.B230400: + s = 'B230400' + elif attr == termios.B460800: + s = 'B460800' + else: + s = 'unknown' + return s + + if self.attr is None: + return 'None' + s = 'iflag: %s' % (_input(self.attr[0])) + s += os.linesep + 'oflag: %s' % (_output(self.attr[1])) + s += os.linesep + 'cflag: %s' % (_control(self.attr[2])) + s += os.linesep + 'lflag: %s' % (_local(self.attr[3])) + s += os.linesep + 'ispeed: %s' % (_baudrate(self.attr[4])) + s += os.linesep + 'ospeed: %s' % (_baudrate(self.attr[5])) + return s + + def _update(self): + self.off() + try: + termios.tcflush(self.fd, termios.TCIOFLUSH) + #attr = self.attr + #attr[0] = termios.IGNPAR; + #attr[1] = 0 + #attr[2] = termios.CRTSCTS | termios.CS8 | termios.CREAD; + #attr[3] = 0 + #attr[6][termios.VMIN] = 1 + #attr[6][termios.VTIME] = 2 + #termios.tcsetattr(self.fd, termios.TCSANOW, attr) + termios.tcsetattr(self.fd, termios.TCSANOW, self.attr) + termios.tcflush(self.fd, termios.TCIOFLUSH) + except: + raise + if self.is_on: + self.on() + + def _baudrate_mask(self, flag): + if flag == 'B0': + mask = termios.B0 + self.attr[5] = termios.B0 + elif flag == 'B50': + mask = termios.B50 + elif flag == 'B75': + mask = termios.B75 + elif flag == 'B110': + mask = termios.B110 + elif flag == 'B134': + mask = termios.B134 + elif flag == 'B150': + mask = termios.B150 + elif flag == 'B200': + mask = termios.B200 + elif flag == 'B300': + mask = termios.B300 + elif flag == 'B600': + mask = termios.B600 + elif flag == 'B1800': + mask = termios.B1800 + elif flag == 'B1200': + mask = termios.B1200 + elif flag == 'B2400': + mask = termios.B2400 + elif flag == 'B4800': + mask = termios.B4800 + elif flag == 'B9600': + mask = termios.B9600 + elif flag == 'B19200': + mask = termios.B19200 + elif flag == 'B38400': + mask = termios.B38400 + elif flag == 'B57600': + mask = termios.B57600 + elif flag == 'B115200': + mask = termios.B115200 + elif flag == 'B230400': + mask = termios.B230400 + elif flag == 'B460800': + mask = termios.B460800 + else: + mask = None + return mask + + def _input_mask(self, flag): + if flag == 'IGNBRK': + mask = termios.IGNBRK + elif flag == 'BRKINT': + mask = termios.BRKINT + elif flag == 'IGNPAR': + mask = termios.IGNPAR + elif flag == 'PARMRK': + mask = termios.PARMRK + elif flag == 'INPCK': + mask = termios.INPCK + elif flag == 'ISTRIP': + mask = termios.ISTRIP + elif flag == 'INLCR': + mask = termios.INLCR + elif flag == 'IGNCR': + mask = termios.IGNCR + elif flag == 'IXON': + mask = termios.IXON + elif flag == 'IXOFF': + mask = termios.IXOFF + elif flag == 'IXANY': + mask = termios.IXANY + elif flag == 'IMAXBEL': + mask = termios.IMAXBEL + else: + mask = None + return mask + + def _output_mask(self, flag): + if flag == 'OPOST': + mask = termios.OPOST + elif flag == 'ONLCR': + mask = termios.ONLCR + elif flag == 'OCRNL': + mask = termios.OCRNL + elif flag == 'TABDLY': + mask = termios.TABDLY + elif flag == 'TAB0': + mask = termios.TAB0 + elif flag == 'TAB3': + mask = termios.TAB3 + elif flag == 'ONOCR': + mask = termios.ONOCR + elif flag == 'ONLRET': + mask = termios.ONLRET + else: + mask = None + return mask + + def _control_mask(self, flag): + if flag == 'CSTOPB': + mask = termios.CSTOPB + elif flag == 'CREAD': + mask = termios.CREAD + elif flag == 'PARENB': + mask = termios.PARENB + elif flag == 'PARODD': + mask = termios.PARODD + elif flag == 'HUPCL': + mask = termios.HUPCL + elif flag == 'CLOCAL': + mask = termios.CLOCAL + elif flag == 'CRTSCTS': + mask = termios.CRTSCTS + else: + mask = None + return mask + + def _local_mask(self, flag): + if flag == 'ECHOKE': + mask = termios.ECHOKE + elif flag == 'ECHOE': + mask = termios.ECHOE + elif flag == 'ECHO': + mask = termios.ECHO + elif flag == 'ECHONL': + mask = termios.ECHONL + elif flag == 'ECHOPRT': + mask = termios.ECHOPRT + elif flag == 'ECHOCTL': + mask = termios.ECHOCTL + elif flag == 'ISIG': + mask = termios.ISIG + elif flag == 'ICANON': + mask = termios.ICANON + elif flag == 'IEXTEN': + mask = termios.IEXTEN + elif flag == 'TOSTOP': + mask = termios.TOSTOP + elif flag == 'FLUSHO': + mask = termios.FLUSHO + elif flag == 'PENDIN': + mask = termios.PENDIN + elif flag == 'NOFLSH': + mask = termios.NOFLSH + else: + mask = None + return mask + + def _set(self, index, mask, state): + if state: + self.attr[index] = self.attr[index] | mask + else: + self.attr[index] = self.attr[index] & ~mask + + def off(self): + if self.fd: + try: + termios.tcflow(self.fd, termios.TCOOFF) + except: + pass + try: + termios.tcflow(self.fd, termios.TCIOFF) + except: + pass + self.is_on = False + + def on(self): + if self.fd: + try: + termios.tcflow(self.fd, termios.TCOON) + except: + pass + try: + termios.tcflow(self.fd, termios.TCION) + except: + pass + self.is_on = True + + def baudrate(self, flag): + mask = self._baudrate_mask(flag) + if mask: + self.attr[4] = mask + self.attr[5] = mask + else: + raise error.general('invalid setting: %s' % (flag)) + self._update() + + def input(self, flag, on): + mask = self._input_mask(flag) + if mask is None: + raise error.general('invalid input flag: %s' % (flag)) + self._set(0, mask, on) + self._update() + + def output(self, flag, on): + mask = self._output_mask(flag) + if mask is None: + raise error.general('invalid output flag: %s' % (flag)) + self._set(1, mask, on) + self._update() + + def control(self, flag, on): + mask = self._control_mask(flag) + if mask is None: + raise error.general('invalid control flag: %s' % (flag)) + self._set(2, mask, on) + self._update() + + def local(self, flag, on): + mask = self._local_mask(flag) + if mask is None: + raise error.general('invalid local flag: %s' % (flag)) + self._set(3, mask, on) + self._update() + + def vmin(self, _vmin): + self.attr[6][termios.VMIN] = _vmin + + def vtime(self, _vtime): + self.attr[6][termios.VTIME] = _vtime + + def set(self, flags): + for f in flags.split(','): + if len(f) < 2: + raise error.general('invalid flag: %s' % (f)) + if f[0] == '~': + on = False + flag = f[1:] + else: + on = True + flag = f + if f.startswith('VMIN'): + vs = f.split('=') + if len(vs) != 2: + raise error.general('invalid vmin flag: %s' % (f)) + try: + _vmin = int(vs[1]) + except: + raise error.general('invalid vmin flag: %s' % (f)) + self.vmin(_vmin) + continue + if f.startswith('VTIME'): + vs = f.split('=') + if len(vs) != 2: + raise error.general('invalid vtime flag: %s' % (f)) + try: + _vtime = int(vs[1]) + except: + raise error.general('invalid vtime flag: %s' % (f)) + self.vtime(_vtime) + continue + mask = self._baudrate_mask(flag) + if mask: + if not on: + raise error.general('baudrates are not flags: %s' % (f)) + self.attr[4] = mask + self.attr[5] = mask + continue + mask = self._input_mask(flag) + if mask: + self._set(0, mask, on) + continue + mask = self._output_mask(flag) + if mask: + self._set(1, mask, on) + continue + mask = self._control_mask(flag) + if mask: + self._set(2, mask, on) + continue + mask = self._local_mask(flag) + if mask: + self._set(3, mask, on) + continue + raise error.general('unknown tty flag: %s' % (f)) + self._update() + +if __name__ == "__main__": + if len(sys.argv) == 2: + t = tty(sys.argv[1]) + t.baudrate('B115200') + t.input('BRKINT', False) + t.input('IGNBRK', True) + t.input('IGNCR', True) + t.local('ICANON', False) + t.local('ISIG', False) + t.local('IEXTEN', False) + t.local('ECHO', False) + t.control('CLOCAL', True) + t.control('CRTSCTS', False) + t.vmin(1) + t.vtime(2) + print t + t.set('B115200,~BRKINT,IGNBRK,IGNCR,~ICANON,~ISIG,~IEXTEN,~ECHO,CLOCAL,~CRTSCTS') + print t + t.on() + while True: + c = t.fd.read(1) + sys.stdout.write(c) diff --git a/tester/rt/test.py b/tester/rt/test.py new file mode 100644 index 0000000..15da4ee --- /dev/null +++ b/tester/rt/test.py @@ -0,0 +1,304 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2013-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import copy +import datetime +import os +import sys +import threading +import time + +from rtemstoolkit import error +from rtemstoolkit import log +from rtemstoolkit import path + +import config +import console +import options +import report +import version + +def stacktraces(): + import traceback + code = [] + for threadId, stack in sys._current_frames().items(): + code.append("\n# thread-id: %s" % threadId) + for filename, lineno, name, line in traceback.extract_stack(stack): + code.append('file: "%s", line %d, in %s' % (filename, lineno, name)) + if line: + code.append(" %s" % (line.strip())) + return '\n'.join(code) + +class test(object): + def __init__(self, index, total, report, executable, rtems_tools, bsp, bsp_config, opts): + self.index = index + self.total = total + self.report = report + self.bsp = bsp + self.bsp_config = bsp_config + self.opts = copy.copy(opts) + self.opts.defaults['test_index'] = str(index) + self.opts.defaults['test_total'] = str(total) + self.opts.defaults['bsp'] = bsp + self.opts.defaults['bsp_arch'] = '%%{%s_arch}' % (bsp) + self.opts.defaults['bsp_opts'] = '%%{%s_opts}' % (bsp) + if not path.isfile(executable): + raise error.general('cannot find executable: %s' % (executable)) + self.opts.defaults['test_executable'] = executable + if rtems_tools: + rtems_tools_bin = path.join(rtems_tools, 'bin') + if not path.isdir(rtems_tools_bin): + raise error.general('cannot find RTEMS tools path: %s' % (rtems_tools_bin)) + self.opts.defaults['rtems_tools'] = rtems_tools_bin + self.config = config.file(report, bsp_config, self.opts) + +class test_run(object): + def __init__(self, index, total, report, executable, rtems_tools, bsp, bsp_config, opts): + self.test = None + self.result = None + self.start_time = None + self.end_time = None + self.index = copy.copy(index) + self.total = total + self.report = report + self.executable = copy.copy(executable) + self.rtems_tools = rtems_tools + self.bsp = bsp + self.bsp_config = bsp_config + self.opts = opts + + def runner(self): + self.start_time = datetime.datetime.now() + try: + self.test = test(self.index, self.total, self.report, + self.executable, self.rtems_tools, + self.bsp, self.bsp_config, + self.opts) + except KeyboardInterrupt: + pass + except: + self.result = sys.exc_info() + self.end_time = datetime.datetime.now() + + def run(self): + self.thread = threading.Thread(target = self.runner, + name = 'test[%s]' % path.basename(self.executable)) + self.thread.start() + + def is_alive(self): + return self.thread and self.thread.is_alive() + + def reraise(self): + if self.result is not None: + raise self.result[0], self.result[1], self.result[2] + +def find_executables(paths): + executables = [] + for p in paths: + if path.isfile(p): + executables += [p] + elif path.isdir(p): + for root, dirs, files in os.walk(p, followlinks = True): + for f in files: + if f.lower().endswith('.exe'): + executables += [path.join(root, f)] + return sorted(executables) + +def report_finished(reports, report_mode, reporting, finished, job_trace): + processing = True + while processing: + processing = False + reported = [] + for tst in finished: + if tst not in reported and \ + (reporting < 0 or tst.index == reporting): + if job_trace: + log.notice('}} %*d: %s: %s (%d)' % (len(str(tst.total)), tst.index, + path.basename(tst.executable), + 'reporting', + reporting)) + processing = True + reports.log(tst.executable, report_mode) + reported += [tst] + reporting += 1 + finished[:] = [t for t in finished if t not in reported] + if len(reported): + del reported[:] + if job_trace: + print '}} threading:', threading.active_count() + for t in threading.enumerate(): + print '}} ', t.name + return reporting + +def _job_trace(tst, msg, total, exe, active, reporting): + s = '' + for a in active: + s += ' %d:%s' % (a.index, path.basename(a.executable)) + log.notice('}} %*d: %s: %s (%d %d %d%s)' % (len(str(tst.total)), tst.index, + path.basename(tst.executable), + msg, + reporting, total, exe, s)) + +def list_bsps(opts): + path_ = opts.defaults.expand('%%{_configdir}/bsps/*.mc') + bsps = path.collect_files(path_) + log.notice(' BSP List:') + for bsp in bsps: + log.notice(' %s' % (path.basename(bsp[:-3]))) + raise error.exit() + +def run(command_path = None): + import sys + stdtty = console.save() + opts = None + try: + optargs = { '--rtems-tools': 'The path to the RTEMS tools', + '--rtems-bsp': 'The RTEMS BSP to run the test on', + '--report-mode': 'Reporting modes, failures (default),all,none', + '--list-bsps': 'List the supported BSPs', + '--debug-trace': 'Debug trace based on specific flags', + '--stacktrace': 'Dump a stack trace on a user termination (^C)' } + opts = options.load(sys.argv, + optargs = optargs, + command_path = command_path) + log.notice('RTEMS Testing - Tester, v%s' % (version.str())) + if opts.find_arg('--list-bsps'): + list_bsps(opts) + opts.log_info() + debug_trace = opts.find_arg('--debug-trace') + if debug_trace: + debug_trace = debug_trace[1] + else: + debug_trace = '' + opts.defaults['debug_trace'] = debug_trace + job_trace = 'jobs' in debug_trace.split(',') + rtems_tools = opts.find_arg('--rtems-tools') + if rtems_tools: + rtems_tools = rtems_tools[1] + bsp = opts.find_arg('--rtems-bsp') + if bsp is None: + raise error.general('no RTEMS BSP provided') + opts.defaults.load('%%{_configdir}/bsps/%s.mc' % (bsp[1])) + bsp = opts.defaults.get('%{bsp}') + if not bsp: + raise error.general('BSP definition (%{bsp}) not found in the global map') + bsp = bsp[2] + if not opts.defaults.set_read_map(bsp): + raise error.general('no BSP map found') + bsp_script = opts.defaults.get(bsp) + if not bsp_script: + raise error.general('BSP script not found: %s' % (bsp)) + bsp_config = opts.defaults.expand(opts.defaults[bsp]) + report_mode = opts.find_arg('--report-mode') + if report_mode: + if report_mode[1] != 'failures' and \ + report_mode[1] != 'all' and \ + report_mode[1] != 'none': + raise error.general('invalid report mode') + report_mode = report_mode[1] + else: + report_mode = 'failures' + executables = find_executables(opts.params()) + if len(executables) == 0: + raise error.general('no executbles supplied') + start_time = datetime.datetime.now() + total = len(executables) + reports = report.report(total) + invalid_tests = opts.defaults['invalid_tests'] + if invalid_tests: + reports.set_invalid_tests([l.strip() for l in invalid_tests.splitlines()]) + reporting = 1 + jobs = int(opts.jobs(opts.defaults['_ncpus'])) + exe = 0 + tests = [] + finished = [] + if jobs > len(executables): + jobs = len(executables) + while exe < total or len(tests) > 0: + if exe < total and len(tests) < jobs: + tst = test_run(exe + 1, total, reports, + executables[exe], + rtems_tools, bsp, bsp_config, + opts) + exe += 1 + tests += [tst] + if job_trace: + _job_trace(tst, 'create', + total, exe, tests, reporting) + tst.run() + else: + dead = [t for t in tests if not t.is_alive()] + tests[:] = [t for t in tests if t not in dead] + for tst in dead: + if job_trace: + _job_trace(tst, 'dead', + total, exe, tests, reporting) + finished += [tst] + tst.reraise() + del dead + if len(tests) >= jobs or exe >= total: + time.sleep(0.250) + if len(finished): + reporting = report_finished(reports, + report_mode, + reporting, + finished, + job_trace) + finished_time = datetime.datetime.now() + reporting = report_finished(reports, report_mode, + reporting, finished, job_trace) + if reporting < total: + log.warning('finished jobs does match: %d' % (reporting)) + report_finished(reports, report_mode, -1, finished, job_trace) + reports.summary() + end_time = datetime.datetime.now() + log.notice('Testing time: %s' % (str(end_time - start_time))) + except error.general, gerr: + print gerr + sys.exit(1) + except error.internal, ierr: + print ierr + sys.exit(1) + except error.exit, eerr: + sys.exit(2) + except KeyboardInterrupt: + if opts.find_arg('--stacktrace'): + print '}} dumping:', threading.active_count() + for t in threading.enumerate(): + print '}} ', t.name + print stacktraces() + log.notice('abort: user terminated') + sys.exit(1) + finally: + console.restore(stdtty) + sys.exit(0) + +if __name__ == "__main__": + run() diff --git a/tester/rt/version.py b/tester/rt/version.py new file mode 100644 index 0000000..7c82de3 --- /dev/null +++ b/tester/rt/version.py @@ -0,0 +1,48 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2013-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Manage paths locally. The internally the path is in Unix or shell format and +# we convert to the native format when performing operations at the Python +# level. This allows macro expansion to work. +# + +major = 0 +minor = 2 +revision = 0 + +def str(): + return '%d.%d.%d'% (major, minor, revision) + +if __name__ == '__main__': + print 'major = %d' % (major) + print 'minor = %d' % (minor) + print 'revision = %d' % (revision) + print 'Version: %s' % (str()) diff --git a/tester/rtems-test b/tester/rtems-test new file mode 100755 index 0000000..53c81cd --- /dev/null +++ b/tester/rtems-test @@ -0,0 +1,42 @@ +#! /usr/bin/env python +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2013 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import sys, os +base = os.path.dirname(os.path.abspath(sys.argv[0])) +parent = os.path.dirname(base) +sys.path = [base, parent] + sys.path + +try: + import rt.test + rt.test.run() +except ImportError: + print >> sys.stderr, "Incorrect RTEMS Tools installation" + sys.exit(1) diff --git a/tester/rtems/testing/bsps/mcf5235.mc b/tester/rtems/testing/bsps/mcf5235.mc new file mode 100644 index 0000000..f95fffb --- /dev/null +++ b/tester/rtems/testing/bsps/mcf5235.mc @@ -0,0 +1,63 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# All paths in defaults must be Unix format. Do not store any Windows format +# paths in the defaults. +# +# Every entry must describe the type of checking a host must pass. +# +# Records: +# key: type, attribute, value +# type : none, dir, exe, triplet +# attribute: none, required, optional +# value : 'single line', '''multi line''' +# + +# +# The Xilinx Zync ZC706 board connected via OpenOCD and a JTAG pod. The console +# is connected to a tty device. +# +[global] +bsp: none, none, 'mcf5235' +jobs: none, none, '1' + +[mcf5235] +mcf5235: none, none, '%{_rtscripts}/gdb.cfg' +mcf5235_arch: none, none, 'm68k' +bsp_tty_dev: none, none, '/dev/cuaU2' +bsp_tty_settings: none, none, 'B19200,~BRKINT,IGNBRK,IGNCR,~ICANON,~ISIG,~IEXTEN,~ECHO,~CLOCAL,VMIN=1,VTIME=2' +gdb_script: none, none, 'mcf5235_gdb_script' +mcf5235_gdb_script: none, none, '''target remote | m68k-bdm-gdbserver pipe 003-005 + thb *0xffe254c0 + continue + load + b bsp_reset + continue''' diff --git a/tester/rtems/testing/bsps/realview_pbx_a9_qemu.mc b/tester/rtems/testing/bsps/realview_pbx_a9_qemu.mc new file mode 100644 index 0000000..5a748c7 --- /dev/null +++ b/tester/rtems/testing/bsps/realview_pbx_a9_qemu.mc @@ -0,0 +1,53 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# All paths in defaults must be Unix format. Do not store any Windows format +# paths in the defaults. +# +# Every entry must describe the type of checking a host must pass. +# +# Records: +# key: type, attribute, value +# type : none, dir, exe, triplet +# attribute: none, required, optional +# value : 'single line', '''multi line''' +# + +# +# The Realview PBX A9 BSP. +# +[global] +bsp: none, none, 'realview_pbx_a9_qemu' + +[realview_pbx_a9_qemu] +realview_pbx_a9_qemu: none, none, '%{_rtscripts}/qemu.cfg' +realview_pbx_a9_qemu_arch: none, none, 'arm' +realview_pbx_a9_qemu_opts: none, none, '-no-reboot -serial /dev/null -serial mon:stdio -net none -nographic -M xilinx-zynq-a9 -m 256M' diff --git a/tester/rtems/testing/bsps/sis-run.mc b/tester/rtems/testing/bsps/sis-run.mc new file mode 100644 index 0000000..cbfcd27 --- /dev/null +++ b/tester/rtems/testing/bsps/sis-run.mc @@ -0,0 +1,54 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# All paths in defaults must be Unix format. Do not store any Windows format +# paths in the defaults. +# +# Every entry must describe the type of checking a host must pass. +# +# Records: +# key: type, attribute, value +# type : none, dir, exe, triplet +# attribute: none, required, optional +# value : 'single line', '''multi line''' +# + +# +# The SIS BSP +# +[global] +bsp: none, none, 'sis' + +[sis] +sis: none, none, '%{_rtscripts}/run.cfg' +sis_arch: none, none, 'sparc' +bsp_run_cmd: none, none, '%{rtems_tools}/%{bsp_arch}-rtems%{rtems_version}-run' +bsp_run_opts: none, none, '-a -nouartrx' diff --git a/tester/rtems/testing/bsps/sis.mc b/tester/rtems/testing/bsps/sis.mc new file mode 100644 index 0000000..ca84acd --- /dev/null +++ b/tester/rtems/testing/bsps/sis.mc @@ -0,0 +1,56 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# All paths in defaults must be Unix format. Do not store any Windows format +# paths in the defaults. +# +# Every entry must describe the type of checking a host must pass. +# +# Records: +# key: type, attribute, value +# type : none, dir, exe, triplet +# attribute: none, required, optional +# value : 'single line', '''multi line''' +# + +# +# The SIS BSP +# +[global] +bsp: none, none, 'sis' + +[sis] +sis: none, none, '%{_rtscripts}/gdb.cfg' +sis_arch: none, none, 'sparc' +gdb_script: none, none, 'sis_gdb_script' +sis_gdb_script: none, none, '''target sim + load + run''' diff --git a/tester/rtems/testing/bsps/xilinx_zynq_a9_qemu.mc b/tester/rtems/testing/bsps/xilinx_zynq_a9_qemu.mc new file mode 100644 index 0000000..22c2a88 --- /dev/null +++ b/tester/rtems/testing/bsps/xilinx_zynq_a9_qemu.mc @@ -0,0 +1,54 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# All paths in defaults must be Unix format. Do not store any Windows format +# paths in the defaults. +# +# Every entry must describe the type of checking a host must pass. +# +# Records: +# key: type, attribute, value +# type : none, dir, exe, triplet +# attribute: none, required, optional +# value : 'single line', '''multi line''' +# + +# +# The Xilinx Zynq A9 QEMU BSP +# +[global] +bsp: none, none, 'xilinx_zynq_a9_qemu' + +[xilinx_zynq_a9_qemu] +xilinx_zynq_a9_qemu: none, none, '%{_rtscripts}/run.cfg' +xilinx_zynq_a9_qemu_arch: none, none, 'arm' +bsp_run_cmd: none, none, 'qemu-system-arm' +bsp_run_opts: none, none, '-no-reboot -serial null -serial mon:stdio -nographic -net none -M xilinx-zynq-a9 -m 256M -kernel' diff --git a/tester/rtems/testing/bsps/xilinx_zynq_a9_qemu_smp.mc b/tester/rtems/testing/bsps/xilinx_zynq_a9_qemu_smp.mc new file mode 100644 index 0000000..bdd2ce7 --- /dev/null +++ b/tester/rtems/testing/bsps/xilinx_zynq_a9_qemu_smp.mc @@ -0,0 +1,55 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# All paths in defaults must be Unix format. Do not store any Windows format +# paths in the defaults. +# +# Every entry must describe the type of checking a host must pass. +# +# Records: +# key: type, attribute, value +# type : none, dir, exe, triplet +# attribute: none, required, optional +# value : 'single line', '''multi line''' +# + +# +# The Xilinx Zynq A9 QEMU BSP +# +[global] +bsp: none, none, 'xilinx_zynq_a9_qemu' + +[xilinx_zynq_a9_qemu] +xilinx_zynq_a9_qemu: none, none, '%{_rtscripts}/run.cfg' +xilinx_zynq_a9_qemu_arch: none, none, 'arm' +bsp_run_cmd: none, none, 'qemu-system-arm' +bsp_run_opts: none, none, '-no-reboot -serial null -serial mon:stdio -nographic -net none -M xilinx-zynq-a9 -m 256M -smp cpus=2 -kernel' +jobs: none, none, 'half' diff --git a/tester/rtems/testing/bsps/xilinx_zynq_zc706.mc b/tester/rtems/testing/bsps/xilinx_zynq_zc706.mc new file mode 100644 index 0000000..fc427ff --- /dev/null +++ b/tester/rtems/testing/bsps/xilinx_zynq_zc706.mc @@ -0,0 +1,65 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# All paths in defaults must be Unix format. Do not store any Windows format +# paths in the defaults. +# +# Every entry must describe the type of checking a host must pass. +# +# Records: +# key: type, attribute, value +# type : none, dir, exe, triplet +# attribute: none, required, optional +# value : 'single line', '''multi line''' +# + +# +# The Xilinx Zync ZC706 board connected via OpenOCD and a JTAG pod. The console +# is connected to a tty device. +# +[global] +bsp: none, none, 'xilinx_zynq_zc706' +jobs: none, none, '1' + +[xilinx_zynq_zc706] +xilinx_zynq_zc706: none, none, '%{_rtscripts}/gdb.cfg' +xilinx_zynq_zc706_arch: none, none, 'arm' +#bsp_tty_dev: none, none, '/dev/cuaU0' +bsp_tty_dev: none, none, '/dev/cu.SLAB_USBtoUART' +gdb_script: none, none, 'xilinx_zynq_zc706_gdb_script' +xilinx_zynq_zc706_gdb_script: none, none, '''target remote kaka:3333 +mon load_image /home/chris/development/si/work/hydra/boot/xilinx-zynq-fsbl/build/arm-rtems4.11-xilinx_zynq_zc706/hydra-fsbl.elf 0 elf + mon resume 0 + mon sleep 4000 + mon halt + load + b bsp_reset + continue''' diff --git a/tester/rtems/testing/console.cfg b/tester/rtems/testing/console.cfg new file mode 100644 index 0000000..c936e5c --- /dev/null +++ b/tester/rtems/testing/console.cfg @@ -0,0 +1,60 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Consoles +# +# The rtems-test command can use a number of consoles. This file +# manages the BSP and user configuration of the console. +# + +# +# Backends can force the console to STDIO and igore any user settings. +# +%if %{defined console_stdio} + %if %{defined console_user_tty} + %warning command line TTY setting ignored + %endif + %console stdio +%else + # + # Console is TTY. + # + %if %{defined bsp_tty_dev} + %define tty_dev %{bsp_tty_dev} + %define tty_defaults B115200,~BRKINT,IGNBRK,IGNCR,~ICANON,~ISIG,~IEXTEN,~ECHO,CLOCAL,~CRTSCTS,VMIN=1,VTIME=2 + %if %{defined bsp_tty_settings} + %define tty_settings %{bsp_tty_settings} + %else + %define tty_settings %{tty_defaults} + %endif + %console tty %{tty_dev} %{tty_settings} + %endif +%endif diff --git a/tester/rtems/testing/defaults.mc b/tester/rtems/testing/defaults.mc new file mode 100644 index 0000000..bf220f3 --- /dev/null +++ b/tester/rtems/testing/defaults.mc @@ -0,0 +1,127 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# All paths in defaults must be Unix format. Do not store any Windows format +# paths in the defaults. +# +# Every entry must describe the type of checking a host must pass. +# +# Records: +# key: type, attribute, value +# type : none, dir, exe, triplet +# attribute: none, required, optional +# value : 'single line', '''multi line''' +# + +# +# Global defaults +# +[global] + +# Nothing +nil: none, none, '' + +# Paths +_topdir: dir, required, '%{_cwd}' +_docdir: dir, none, '%{_defaultdocdir}' +_tmppath: dir, none, '%{_topdir}/build/tmp' +_tmproot: dir, none, '%{_tmppath}/rt/%{_bset}' +_datadir: dir, none, '%{_prefix}/share' +_defaultdocdir: dir, none, '%{_prefix}/share/doc' +_exeext: none, none, '' +_exec_prefix: dir, none, '%{_prefix}' +_bindir: dir, none, '%{_exec_prefix}/bin' +_sbindir: dir, none, '%{_exec_prefix}/sbin' +_libexecdir: dir, none, '%{_exec_prefix}/libexec' +_datarootdir: dir, none, '%{_prefix}/share' +_datadir: dir, none, '%{_datarootdir}' +_sysconfdir: dir, none, '%{_prefix}/etc' +_sharedstatedir: dir, none, '%{_prefix}/com' +_localstatedir: dir, none, '%{prefix}/var' +_includedir: dir, none, '%{_prefix}/include' +_lib: dir, none, 'lib' +_libdir: dir, none, '%{_exec_prefix}/%{_lib}' +_libexecdir: dir, none, '%{_exec_prefix}/libexec' +_mandir: dir, none, '%{_datarootdir}/man' +_infodir: dir, none, '%{_datarootdir}/info' +_localedir: dir, none, '%{_datarootdir}/locale' +_localedir: dir, none, '%{_datadir}/locale' +_localstatedir: dir, none, '%{_prefix}/var' +_prefix: dir, none, '%{_usr}' +_usr: dir, none, '/usr/local' +_usrsrc: dir, none, '%{_usr}/src' +_var: dir, none, '/usr/local/var' +_varrun: dir, none, '%{_var}/run' + +# Defaults, override in platform specific modules. +__arch_install_post: exe, none, '%{nil}' +__bash: exe, optional, '/bin/bash' +__bzip2: exe, required, '/usr/bin/bzip2' +__cat: exe, required, '/bin/cat' +__chgrp: exe, required, '/usr/bin/chgrp' +__chmod: exe, required, '/bin/chmod' +__chown: exe, required, '/usr/sbin/chown' +__cp: exe, required, '/bin/cp' +__git: exe, required, '/usr/bin/git' +__grep: exe, required, '/usr/bin/grep' +__gzip: exe, required, '/usr/bin/gzip' +__id: exe, required, '/usr/bin/id' +__id_u: exe, none, '%{__id} -u' +__ln_s: exe, none, 'ln -s' +__make: exe, required, 'make' +__mkdir: exe, required, '/bin/mkdir' +__mkdir_p: exe, none, '/bin/mkdir -p' +__mv: exe, required, '/bin/mv' +__patch_bin: exe, required, '/usr/bin/patch' +__patch_opts: none, none, '%{nil}' +__patch: exe, none, '%{__patch_bin} %{__patch_opts}' +__svn: exe, optional, '/usr/bin/svn' +__rm: exe, required, '/bin/rm' +__rmfile: exe, none, '%{__rm} -f' +__rmdir: exe, none, '%{__rm} -rf' +__sed: exe, required, '/usr/bin/sed' +__sh: exe, required, '/bin/sh' +__tar: exe, required, '/usr/bin/tar' +__tar_extract: exe, none, '%{__tar} -xvvf' +__touch: exe, required, '/usr/bin/touch' +__unzip: exe, required, '/usr/bin/unzip' +__xz: exe, required, '/usr/bin/xz' + +# Default settings +_target: none, none, '%{nil}' + +# Paths +_rtbase: none, none, '%{_rtdir}' +_rttesting: none, none, '%{_rtbase}/rtems/testing' +_configdir: none, none, '%{_rtbase}/config:%{_rttesting}' + +# Include the testing macros. +%include %{_rttesting}/testing.mc diff --git a/tester/rtems/testing/gdb.cfg b/tester/rtems/testing/gdb.cfg new file mode 100644 index 0000000..b680dba --- /dev/null +++ b/tester/rtems/testing/gdb.cfg @@ -0,0 +1,60 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Run +# +# Use a run command to run the executable. The run command is a GDB based +# simulator that is packaged as a single command that executes the program. +# + +%include %{_configdir}/base.cfg +%include %{_configdir}/checks.cfg + +# +# Console. +# +%include %{_configdir}/console.cfg + +# +# RTEMS version +# +%include %{_rtdir}/rtems/version.cfg + +# +# GDB executable +# +%define gdb_cmd %{rtems_tools}/%{bsp_arch}-rtems%{rtems_version}-gdb + +# +# GDB, pass the GDB command, the text executable and the macro label +# for the script. +# +%gdb %{gdb_cmd} %{test_executable} %{gdb_script} diff --git a/tester/rtems/testing/qemu.cfg b/tester/rtems/testing/qemu.cfg new file mode 100644 index 0000000..e15f058 --- /dev/null +++ b/tester/rtems/testing/qemu.cfg @@ -0,0 +1,60 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# QEMU +# +# Use a qemu command to run the executable in the qemu simulator. +# + +%include %{_configdir}/base.cfg +%include %{_configdir}/checks.cfg + +# +# Console. +# +%define console_stdio +%include %{_configdir}/console.cfg + +# +# RTEMS version +# +%include %{_rtdir}/rtems/version.cfg + +# +# Qemu executable +# +%define qemu_cmd qemu-system-%{bsp_arch} +%define qemu_opts %{bsp_opts} + +# +# Executable +# +%execute %{qemu_cmd} %{qemu_opts} -kernel %{test_executable} diff --git a/tester/rtems/testing/run.cfg b/tester/rtems/testing/run.cfg new file mode 100644 index 0000000..f7b21d7 --- /dev/null +++ b/tester/rtems/testing/run.cfg @@ -0,0 +1,70 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# Run +# +# Use a run command to run the executable. The run command is a GDB based +# simulator that is packaged as a single command that executes the program. +# + +%include %{_configdir}/base.cfg +%include %{_configdir}/checks.cfg + +# +# Console. +# +%define console_stdio +%include %{_configdir}/console.cfg + +# +# RTEMS version +# +%include %{_rtdir}/rtems/version.cfg + +# +# Run executable. +# +%ifn %{defined bsp_run_cmd} + %error No BSP run command provied. +%endif +%ifn %{defined bsp_run_opts} + %define bsp_run_opts %{nil} +%endif +%define run_cmd %{bsp_run_cmd} +%define run_opts %{bsp_run_opts} + +# +# Executable +# +%ifn %{defined test_executable_opts} + %define test_executable_opts %{nil} +%endif +%execute %{run_cmd} %{run_opts} %{test_executable} %{test_executable_opts} diff --git a/tester/rtems/testing/testing.mc b/tester/rtems/testing/testing.mc new file mode 100644 index 0000000..77f8419 --- /dev/null +++ b/tester/rtems/testing/testing.mc @@ -0,0 +1,57 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# All paths in defaults must be Unix format. Do not store any Windows format +# paths in the defaults. +# +# Every entry must describe the type of checking a host must pass. +# +# Records: +# key: type, attribute, value +# type : none, dir, exe, triplet +# attribute: none, required, optional +# value : 'single line', '''multi line''' +# + +# +# Global defaults +# +[global] + +# Paths +_rtbase: none, none, '%{_rtdir}' +_rtscripts: none, none, '%{_rtbase}/rtems/testing' + +# Defaults +timeout: none, none, '180' + +# Tests detected as invalid that are valid +invalid_tests: none, none, '''minimum.exe''' diff --git a/tester/rtems/version.cfg b/tester/rtems/version.cfg new file mode 100644 index 0000000..46f9939 --- /dev/null +++ b/tester/rtems/version.cfg @@ -0,0 +1,35 @@ +# +# RTEMS Tools Project (http://www.rtems.org/) +# Copyright 2010-2014 Chris Johns (chrisj@rtems.org) +# All rights reserved. +# +# This file is part of the RTEMS Tools package in 'rtems-tools'. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# +# RTEMS Version +# + +%define rtems_version 4.11 |