diff options
author | Chris Johns <chrisj@rtems.org> | 2014-02-15 06:30:06 +1100 |
---|---|---|
committer | Chris Johns <chrisj@rtems.org> | 2014-02-15 06:30:06 +1100 |
commit | 50fdf12244e784bd52732be1e68d966bc9629b24 (patch) | |
tree | 93b6656d397f3ef358b50aeaef6dc4d10a653f8c /rtemstoolkit | |
parent | 8f75c4a380cb0a4330f65966784ccdfeff756e70 (diff) |
rt: Add the rtems-tester.
Diffstat (limited to 'rtemstoolkit')
-rw-r--r-- | rtemstoolkit/.gitignore | 4 | ||||
-rw-r--r-- | rtemstoolkit/__init__.py | 40 | ||||
-rw-r--r-- | rtemstoolkit/check.py | 176 | ||||
-rw-r--r-- | rtemstoolkit/config.py | 857 | ||||
-rw-r--r-- | rtemstoolkit/darwin.py | 79 | ||||
-rw-r--r-- | rtemstoolkit/error.py | 65 | ||||
-rwxr-xr-x | rtemstoolkit/execute.py | 517 | ||||
-rw-r--r-- | rtemstoolkit/freebsd.py | 99 | ||||
-rw-r--r-- | rtemstoolkit/git.py | 201 | ||||
-rw-r--r-- | rtemstoolkit/linux.py | 148 | ||||
-rwxr-xr-x | rtemstoolkit/log.py | 226 | ||||
-rw-r--r-- | rtemstoolkit/macros.py | 495 | ||||
-rw-r--r-- | rtemstoolkit/mailer.py | 120 | ||||
-rw-r--r-- | rtemstoolkit/options.py | 597 | ||||
-rw-r--r-- | rtemstoolkit/path.py | 239 | ||||
-rw-r--r-- | rtemstoolkit/version.py | 48 | ||||
-rw-r--r-- | rtemstoolkit/windows.py | 135 |
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()) |