summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Johns <chrisj@rtems.org>2014-02-15 06:30:06 +1100
committerChris Johns <chrisj@rtems.org>2014-02-15 06:30:06 +1100
commit50fdf12244e784bd52732be1e68d966bc9629b24 (patch)
tree93b6656d397f3ef358b50aeaef6dc4d10a653f8c
parent8f75c4a380cb0a4330f65966784ccdfeff756e70 (diff)
rt: Add the rtems-tester.
-rw-r--r--rtemstoolkit/.gitignore4
-rw-r--r--rtemstoolkit/__init__.py40
-rw-r--r--rtemstoolkit/check.py176
-rw-r--r--rtemstoolkit/config.py857
-rw-r--r--rtemstoolkit/darwin.py79
-rw-r--r--rtemstoolkit/error.py65
-rwxr-xr-xrtemstoolkit/execute.py517
-rw-r--r--rtemstoolkit/freebsd.py99
-rw-r--r--rtemstoolkit/git.py201
-rw-r--r--rtemstoolkit/linux.py148
-rwxr-xr-xrtemstoolkit/log.py226
-rw-r--r--rtemstoolkit/macros.py495
-rw-r--r--rtemstoolkit/mailer.py120
-rw-r--r--rtemstoolkit/options.py597
-rw-r--r--rtemstoolkit/path.py239
-rw-r--r--rtemstoolkit/version.py48
-rw-r--r--rtemstoolkit/windows.py135
-rw-r--r--tester/.gitignore4
-rw-r--r--tester/config/base.cfg35
-rw-r--r--tester/config/checks.cfg37
-rw-r--r--tester/rt/__init__.py30
-rw-r--r--tester/rt/config.py205
-rw-r--r--tester/rt/console.py143
-rw-r--r--tester/rt/gdb.py321
-rw-r--r--tester/rt/options.py118
-rw-r--r--tester/rt/pygdb/__init__.py21
-rwxr-xr-xtester/rt/pygdb/mi_parser.py423
-rw-r--r--tester/rt/pygdb/spark.py847
-rw-r--r--tester/rt/report.py185
-rw-r--r--tester/rt/stty.py567
-rw-r--r--tester/rt/test.py304
-rw-r--r--tester/rt/version.py48
-rwxr-xr-xtester/rtems-test42
-rw-r--r--tester/rtems/testing/bsps/mcf5235.mc63
-rw-r--r--tester/rtems/testing/bsps/realview_pbx_a9_qemu.mc53
-rw-r--r--tester/rtems/testing/bsps/sis-run.mc54
-rw-r--r--tester/rtems/testing/bsps/sis.mc56
-rw-r--r--tester/rtems/testing/bsps/xilinx_zynq_a9_qemu.mc54
-rw-r--r--tester/rtems/testing/bsps/xilinx_zynq_a9_qemu_smp.mc55
-rw-r--r--tester/rtems/testing/bsps/xilinx_zynq_zc706.mc65
-rw-r--r--tester/rtems/testing/console.cfg60
-rw-r--r--tester/rtems/testing/defaults.mc127
-rw-r--r--tester/rtems/testing/gdb.cfg60
-rw-r--r--tester/rtems/testing/qemu.cfg60
-rw-r--r--tester/rtems/testing/run.cfg70
-rw-r--r--tester/rtems/testing/testing.mc57
-rw-r--r--tester/rtems/version.cfg35
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