aboutsummaryrefslogtreecommitdiff
path: root/mconfig.py
diff options
context:
space:
mode:
Diffstat (limited to 'mconfig.py')
-rw-r--r--mconfig.py266
1 files changed, 231 insertions, 35 deletions
diff --git a/mconfig.py b/mconfig.py
index 09f1e00..f8af752 100644
--- a/mconfig.py
+++ b/mconfig.py
@@ -1,4 +1,4 @@
-import re, argparse, sys, os, string
+import re, argparse, sys, os, string, shlex
from collections import OrderedDict, namedtuple
import curses.ascii
@@ -8,7 +8,7 @@ def indentify(s, indent=' '):
def argv_to_shell(argv):
quoteds = []
for arg in argv:
- if re.match('^[a-zA-Z0-9_\.@/+-]+$', arg):
+ if re.match('^[a-zA-Z0-9_\.@/+=-]+$', arg):
quoteds.append(arg)
else:
quoted = ''
@@ -27,13 +27,26 @@ def argv_to_shell(argv):
class Pending(object):
def __str__(self):
return 'Pending',
+ def resolve(self):
+ return self.value
+ # xxx py3
+ def __getattr__(self, attr):
+ return PendingAttribute(self, attr)
class PendingOption(Pending, namedtuple('PendingOption', 'opt')):
def need(self):
self.opt.need()
- def __str__(self):
+ def resolve(self):
+ return self.opt.value
+ def __repr__(self):
return 'PendingOption(%s)' % (self.opt.name,)
+class PendingAttribute(Pending, namedtuple('PendingAttribute', 'base attr')):
+ def need(self):
+ self.base.need()
+ def resolve(self):
+ return getattr(self.base, self.attr)
+
class SettingsGroup(object):
def __init__(self, group_parent=None, inherit_parent=None, name=None):
object.__setattr__(self, 'group_parent', group_parent)
@@ -43,7 +56,8 @@ class SettingsGroup(object):
name = '<0x%x>' % (id(self),)
object.__setattr__(self, 'name', name)
@staticmethod
- def get_meat(self, attr, exctype=KeyError, allow_pending=False):
+ def get_meat(self, attr, exctype=KeyError):
+ allow_pending = not did_parse_args
try:
obj = object.__getattribute__(self, 'vals')[attr]
except KeyError:
@@ -55,11 +69,16 @@ class SettingsGroup(object):
return ret
raise exctype(attr)
else:
- if not allow_pending and isinstance(obj, Pending):
- if isinstance(obj, PendingOption):
- raise Exception("setting %r is pending the command line option %r, probably because you didn't 'need' the setting" % (attr, obj.opt))
- else:
- raise Exception("setting %r is pending; you need to set it" % (attr,))
+ if isinstance(obj, Pending):
+ try:
+ return obj.resolve()
+ except:
+ if not allow_pending:
+ if isinstance(obj, PendingOption):
+ raise Exception("setting %r is pending the command line option %r, probably because you didn't 'need' the setting" % (attr, obj.opt))
+ else:
+ raise Exception("setting %r is pending; you need to set it" % (attr,))
+ return obj
return obj
def __getattribute__(self, attr):
try:
@@ -97,17 +116,11 @@ class SettingsGroup(object):
return s
def relative_lookup(self, name):
- if name.startswith('..'):
- return self.group_parent.relative_lookup(name[2:])
- else:
- bits = name.split('.', 1)
- if len(bits) == 1:
- return (self, bits[0])
- else:
- return self[bits[0]].relative_lookup(bits[1])
+ name = re.sub('^\.\.', 'group_parent.', name)
+ return eval('self.' + name)
def need(self, name):
if name not in self.vals:
- raise KeyError('need setting that has not been set')
+ raise KeyError('need setting %r that has not been set' % (name,))
obj = self.vals[name]
if hasattr(obj, 'need'):
obj.need()
@@ -146,7 +159,7 @@ class Option(object):
assert len(kwargs) == 0
assert bool is False
else:
- raise ValueError("name should be '--opt' or 'ENV='")
+ raise ValueError("name %r should be '--opt' or 'ENV='" % (name,))
self.name = name
self.help = help
self.default = default
@@ -165,11 +178,12 @@ class Option(object):
raise KeyError('trying to create Option with duplicate name %r; old is:\n%r' % (name, all_options_by_name[name]))
all_options_by_name[name] = self
def __repr__(self):
- return 'Option(name=%r, help=%r, value=%r, default=%r)' % (self.name, self.help, self.value, self.default)
+ value = repr(self.value) if hasattr(self, 'value') else '<none yet>'
+ return 'Option(name=%r, help=%r, value=%s, default=%r)' % (self.name, self.help, value, self.default)
def set(self, value):
if value is None:
value = self.default
- if callable(value):
+ if callable(value): # Pending
value = value()
self.value = value
if self.on_set is not None:
@@ -181,18 +195,21 @@ class Option(object):
class Expansion(object):
def __init__(self, fmt, base):
+ assert isinstance(fmt, str)
self.fmt = fmt
self.deps = list(map(base.relative_lookup, re.findall('\((.*?)\)', fmt)))
def __repr__(self):
return 'Expansion(%r)' % (self.fmt,)
def need(self):
- for target, attr in self.deps:
- target.need(attr)
+ for dep in self.deps:
+ if hasattr(dep, 'need'):
+ dep.need()
def __call__(self):
deps = self.deps[:]
def get_dep(m):
- target, attr = deps.pop(0)
- return target[attr]
+ dep = deps.pop(0)
+ if isinstance(dep, Pending):
+ dep = dep.resolve()
return re.sub('\((.*?)\)', get_dep, self.fmt)
def installation_dirs_group(sg):
@@ -252,20 +269,26 @@ def _print_help(include_unused=False):
def parse_args():
default_opt_section.move_to_end()
parser = _make_argparse(include_unused=True, include_env=False)
- sys_argv = sys.argv[1:]
- args, argv = parser.parse_known_args(sys_argv)
+ args, argv = parser.parse_known_args()
if args.__help or args.__help_all:
_print_help(include_unused=args.__help_all)
sys.exit(0)
+ unrecognized_env = []
def do_env_arg(arg):
- m = re.match('([^ ]+)=(.*)', arg)
- if m:
+ m = re.match('([^- ]+)=(.*)', arg)
+ if not m:
+ return True # keep for unrecognized
+ if m.group(1) + '=' not in all_options_by_name:
+ unrecognized_env.append(arg)
+ else:
os.environ[m.group(1)] = m.group(2)
- return False
- return True
- argv = list(filter(do_env_arg, argv))
- if argv or sys_argv:
- print ('unrecognized arguments: %s' % (argv_to_shell(argv),))
+ return False
+ unrecognized_argv = list(filter(do_env_arg, argv))
+ if unrecognized_argv:
+ print ('unrecognized arguments: %s' % (argv_to_shell(unrecognized_argv),))
+ if unrecognized_env:
+ print ('unrecognized environment: %s' % (argv_to_shell(unrecognized_env),))
+ if unrecognized_argv or unrecognized_env:
_print_help()
sys.exit(0)
@@ -277,7 +300,173 @@ def parse_args():
opt.set(getattr(args, opt.name[2:]))
#print args._unrecognized_args
-#class
+ global did_parse_args
+ did_parse_args = True
+
+# -- toolchains --
+class Triple(namedtuple('Triple', 'triple arch forgot1 os forgot2')):
+ def __new__(self, triple):
+ if isinstance(triple, Triple):
+ return triple
+ else:
+ bits = triple.split('-')
+ numbits = len(bits)
+ if numbits > 4:
+ raise Exception('strange triple %r' % (triple,))
+ if numbits != 4:
+ bits.insert(1, None)
+ return super(Triple, self).__new__(self, triple, *((bits.pop(0) if bits else None) for i in range(4)))
+ #def __repr__(self):
+ def __str__(self):
+ return self.triple
+
+class Machine(object):
+ def __init__(self, name, settings, triple_help, triple_default):
+ self.name = name
+ self.settings = settings
+ def on_set(val):
+ self.triple = val
+ self.triple_option = Option('--' + name, help=triple_help, default=triple_default, on_set=on_set, type=Triple, section=triple_options_section)
+ self.triple = PendingOption(self.triple_option)
+
+ def __eq__(self, other):
+ return self.triple == other.triple
+ def __ne__(self, other):
+ return self.triple != other.triple
+ def __repr__(self):
+ return 'Machine(name=%r, triple=%s)' % (self.name, repr(self.triple) if hasattr(self, 'triple') else '<none yet>')
+
+ def need(self):
+ self.triple_option.need()
+
+ def is_cross(self):
+ # This is only really meaningful in GNU land, as it decides whether to
+ # prepend the triple (hopefully other targets are sane enough not to
+ # have a special separate "cross compilation mode" that skips
+ # configuration checks, but...). Declared here because it may be
+ # useful to override.
+ if not hasattr(self, '_is_cross'):
+ self._is_cross = self.triple != self.settings.build_machine.triple
+ return self._is_cross
+
+class UnixTool(object):
+ def __init__(self, name, defaults, env, settings, toolchain=None, dont_suffix_env=False):
+ self.name = name
+ self.defaults = defaults
+ self.env = env
+ self.toolchain = toolchain
+ self.needed = False
+ self.settings = settings
+ if toolchain.machine.name != 'host' and not dont_suffix_env:
+ env = '%s_FOR_%s' % (env, toolchain.name.upper())
+ def on_set(val):
+ if val is not None:
+ self.argv = shlex.split(val)
+ self.argv_opt = Option(env + '=', help='Default: %r' % (defaults,), default=None, on_set=on_set)
+ def need(self):
+ if not self.needed:
+ self.needed = True
+ self.argv_opt.need()
+ tests_required.append(self.locate)
+ def __repr__(self):
+ return 'UnixTool(name=%r, defaults=%r, env=%r)' % (self.name, self.defaults, self.env)
+
+ def locate(self):
+ self.do_locate()
+ if hasattr(self, 'argv'):
+ sys.stderr.write('Found %s: %s\n' % (self.name, argv_to_shell(self.argv)))
+ else:
+ sys.stderr.write('** Failed to locate %r\n' % (self,))
+ if self.toolchain is not None and self.toolchain.machine.is_cross():
+ sys.stderr.write(' note: detected cross compilation, so searched for %s-%s\n' % (self.toolchain.machine.triple.triple, self.name))
+
+ return False
+
+ def do_locate(self):
+ # First alternative: if the user specified it explicitly, don't question.
+ if hasattr(self, 'argv'):
+ return
+
+ # Second alternative: the toolchain might have special handling (e.g. OS X).
+ if self.toolchain is not None:
+ argv = self.toolchain.find_tool(self)
+ if argv is not None:
+ self.argv = argv
+ return
+
+ # Third alternative: search a path, either the toolchain's or the default
+ paths = None
+ if self.toolchain is not None:
+ paths = self.toolchain.get_tool_search_paths()
+ if paths is None:
+ paths = self.settings.tool_search_paths
+ argv = self.locate_in_paths(paths)
+ if argv is not None:
+ self.argv = argv
+ return
+ # Give up...
+
+ def locate_in_paths(self, paths):
+ prefix = ''
+ if self.toolchain is not None and self.toolchain.machine.is_cross():
+ prefix = self.toolchain.machine.triple.triple + '-'
+ for path in paths:
+ for default in self.defaults:
+ default = prefix + default
+ filename = os.path.join(path, default)
+ if os.path.exists(filename):
+ return [filename]
+ return None
+
+
+class BasicUnixToolchain(object):
+ def __init__(self, machine, settings):
+ machine.need()
+ self.machine = machine
+ self.tools = []
+ tools = [
+ # TODO figure out ld
+ ('cc', ['cc', 'gcc', 'clang'], 'CC'),
+ ('cxx', ['c++', 'g++', 'clang++'], 'CXX'),
+ ('ar',),
+ ('nm',),
+ ('ranlib',),
+ ('strip',),
+ # GNU
+ ('objdump', ['objdump', 'gobjdump'], 'OBJDUMP'),
+ ('objcopy', ['objcopy', 'gobjcopy'], 'OBJCOPY'),
+ # OS X
+ ('lipo',),
+ ]
+ for spec in tools:
+ if len(spec) == 1:
+ name, defaults, env = spec[0], [spec[0]], spec[0].upper()
+ else:
+ name, defaults, env = spec
+ tool = UnixTool(name, defaults, env, settings, toolchain=self)
+ setattr(self, name, tool)
+ self.tools.append(tool)
+
+ def find_tool(self, tool):
+ return None
+
+ def get_tool_search_paths(self):
+ return None # just use the default
+
+def run_tests():
+ tests_failed = 0
+ for test in tests_required:
+ ret = test()
+ if ret is False:
+ tests_failed += 1
+ if tests_failed > 0:
+ sys.stderr.write('(%s tests failed.)\n' % (tests_failed,))
+ sys.exit(1)
+
+# -- init code --
+
+did_parse_args = False
+tests_required = []
all_options = []
all_options_by_name = {}
@@ -288,5 +477,12 @@ settings_root = SettingsGroup(name='root')
settings_root.package_unix_name = Pending()
installation_dirs_group(settings_root.new_child('idirs'))
+triple_options_section = OptSection('System types:')
+settings_root.build_machine = Machine('build', settings_root, 'the machine doing the build', '')
+settings_root.host_machine = Machine('host', settings_root, 'the machine that will run the compiled program', lambda: settings_root.build_machine.triple)
+# ...'the machine that the program will itself compile programs for',
+
+settings_root.tool_search_paths = os.environ['PATH'].split(':')
+
# --