summaryrefslogtreecommitdiff
path: root/rtemstoolkit
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 /rtemstoolkit
parent8f75c4a380cb0a4330f65966784ccdfeff756e70 (diff)
rt: Add the rtems-tester.
Diffstat (limited to 'rtemstoolkit')
-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
17 files changed, 4046 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())