diff options
author | Chris Larson <chris_larson@mentor.com> | 2010-11-09 14:48:13 -0700 |
---|---|---|
committer | Chris Larson <chris_larson@mentor.com> | 2010-12-28 09:19:51 -0700 |
commit | 606441e5c4f4a979231fb943b1fa39ea5371a727 (patch) | |
tree | fb60c6a2b6d0656812af1cfb3c1b229d808f51b5 /lib | |
parent | 4247d8ca5b818a9b0409667d67b534cead00cb69 (diff) | |
download | openembedded-606441e5c4f4a979231fb943b1fa39ea5371a727.tar.gz |
Implement variable typing
This implementation consists of two components:
- oe.types python module, whose job it is to construct objects of the defined
type for a given variable in the metadata
- typecheck.bbclass, which iterates over all configuration variables with a
type defined and uses oe.types to check the validity of the values
This gives us a few benefits:
- Automatic sanity checking of all configuration variables with a defined type
- Avoid duplicating the "how do I make use of the value of this variable"
logic between its users. For variables like PATH, this is simply a split(),
for boolean variables, the duplication can result in confusing, or even
mismatched semantics (is this 0/1, empty/nonempty, what?)
- Make it easier to create a configuration UI, as the type information could
be used to provide a better interface than a text edit box (e.g checkbox for
'boolean', dropdown for 'choice')
This functionality is entirely opt-in right now. To enable the configuration
variable type checking, simply INHERIT += "typecheck". Example of a failing
type check:
BAZ = "foo"
BAZ[type] = "boolean"
$ bitbake -p
FATAL: BAZ: Invalid boolean value 'foo'
$
Examples of leveraging oe.types in a python snippet:
PACKAGES[type] = "list"
python () {
import oe.types
for pkg in oe.types.value("PACKAGES", d):
bb.note("package: %s" % pkg)
}
LIBTOOL_HAS_SYSROOT = "yes"
LIBTOOL_HAS_SYSROOT[type] = "boolean"
python () {
import oe.types
assert(oe.types.value("LIBTOOL_HAS_SYSROOT", d) == True)
}
Signed-off-by: Chris Larson <chris_larson@mentor.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/oe/_types.py | 59 | ||||
-rw-r--r-- | lib/oe/test_types.py | 56 | ||||
-rw-r--r-- | lib/oe/types.py | 97 |
3 files changed, 212 insertions, 0 deletions
diff --git a/lib/oe/_types.py b/lib/oe/_types.py new file mode 100644 index 0000000000..91afb8ab5d --- /dev/null +++ b/lib/oe/_types.py @@ -0,0 +1,59 @@ +import re + +class OEList(list): + name = "list" + + def __init__(self, value, separator = None): + if value is not None: + list.__init__(self, value.split(separator)) + else: + list.__init__(self) + + if separator is None: + self.separator = " " + else: + self.separator = separator + + def __str__(self): + return self.separator.join(self) + +def choice(value, choices): + if not isinstance(value, basestring): + raise TypeError("choice accepts a string, not '%s'" % type(value)) + + value = value.lower() + choices = choices.lower() + if value not in choices.split(): + raise ValueError("Invalid choice '%s'. Valid choices: %s" % + (value, choices)) + return value + +def regex(value, regexflags=None): + flagval = 0 + if regexflags: + for flag in regexflags.split(): + flag = flag.upper() + try: + flagval |= getattr(re, flag) + except AttributeError: + raise ValueError("Invalid regex flag '%s'" % flag) + + try: + return re.compile(value, flagval) + except re.error, exc: + raise ValueError("Invalid regex value '%s': %s" % + (value, exc.args[0])) + +def boolean(value): + if not isinstance(value, basestring): + raise TypeError("boolean accepts a string, not '%s'" % type(value)) + + value = value.lower() + if value in ('yes', 'y', 'true', 't', '1'): + return True + elif value in ('no', 'n', 'false', 'f', '0'): + return False + raise ValueError("Invalid boolean value '%s'" % value) + +def integer(value): + return int(value) diff --git a/lib/oe/test_types.py b/lib/oe/test_types.py new file mode 100644 index 0000000000..494abfae43 --- /dev/null +++ b/lib/oe/test_types.py @@ -0,0 +1,56 @@ +import unittest +from oe.types import create, factory + +class TestTypes(unittest.TestCase): + def assertIsInstance(self, obj, cls): + return self.assertTrue(isinstance(obj, cls)) + + def assertIsNot(self, obj, other): + return self.assertFalse(obj is other) + + def assertFactoryCreated(self, value, type, **flags): + obj = factory(type) + self.assertIsNot(obj, None) + self.assertIsInstance(create(value, type, **flags), obj) + +class TestBooleanType(TestTypes): + def test_boolean(self): + self.assertRaises(ValueError, create, '', 'boolean') + self.assertRaises(ValueError, create, 'foo', 'boolean') + self.assertRaises(TypeError, create, object(), 'boolean') + + def test_boolean_true(self): + self.assertEqual(create('y', 'boolean'), True) + self.assertEqual(create('yes', 'boolean'), True) + self.assertEqual(create('1', 'boolean'), True) + self.assertEqual(create('t', 'boolean'), True) + self.assertEqual(create('true', 'boolean'), True) + self.assertEqual(create('TRUE', 'boolean'), True) + self.assertEqual(create('truE', 'boolean'), True) + + def test_boolean_false(self): + self.assertEqual(create('n', 'boolean'), False) + self.assertEqual(create('no', 'boolean'), False) + self.assertEqual(create('0', 'boolean'), False) + self.assertEqual(create('f', 'boolean'), False) + self.assertEqual(create('false', 'boolean'), False) + self.assertEqual(create('FALSE', 'boolean'), False) + self.assertEqual(create('faLse', 'boolean'), False) + +class TestList(TestTypes): + def assertListEqual(self, value, valid, sep=None): + obj = create(value, 'list', separator=sep) + self.assertEqual(obj, valid) + if sep is not None: + self.assertEqual(obj.separator, sep) + self.assertEqual(str(obj), obj.separator.join(obj)) + + def test_list_nosep(self): + testlist = ['alpha', 'beta', 'theta'] + self.assertListEqual('alpha beta theta', testlist) + self.assertListEqual('alpha beta\ttheta', testlist) + self.assertListEqual('alpha', ['alpha']) + + def test_list_usersep(self): + self.assertListEqual('foo:bar', ['foo', 'bar'], ':') + self.assertListEqual('foo:bar:baz', ['foo', 'bar', 'baz'], ':') diff --git a/lib/oe/types.py b/lib/oe/types.py new file mode 100644 index 0000000000..963e964212 --- /dev/null +++ b/lib/oe/types.py @@ -0,0 +1,97 @@ +# Constructs objects of the specified type for a given variable in the +# metadata. The 'type' flag of the variable defines which of the factory +# functions in this module will be called. +# +# If no type is defined, the value() function will simply return the expanded +# string value as is. + +import bb +import inspect +import _types + +types = {} + +class MissingFlag(TypeError): + def __init__(self, flag, type): + self.flag = flag + self.type = type + TypeError.__init__(self) + + def __str__(self): + return "Type '%s' requires flag '%s'" % (self.type, self.flag) + +def factory(var_type): + try: + return types[var_type] + except KeyError: + raise TypeError("Invalid type '%s'" % var_type) + +def create(value, var_type, **flags): + obj = factory(var_type) + objflags = {} + for flag in obj.flags: + if flag not in flags: + if flag not in obj.optflags: + raise MissingFlag(flag, var_type) + else: + objflags[flag] = flags[flag] + + return obj(value, **objflags) + +def value(key, d): + """Construct a value for a metadata variable, based upon its flags""" + + var_type = d.getVarFlag(key, 'type') + flags = d.getVarFlags(key) + + try: + return create(d.getVar(key, True) or '', var_type, **flags) + except (TypeError, ValueError), exc: + bb.fatal("%s: %s" % (key, str(exc))) + +def get_callable_args(obj): + """Grab all but the first argument of the specified callable, returning + the list, as well as a list of which of the arguments have default + values.""" + if type(obj) is type: + obj = obj.__init__ + + args, varargs, keywords, defaults = inspect.getargspec(obj) + flaglist = [] + if args: + if len(args) > 1 and args[0] == 'self': + args = args[1:] + flaglist.extend(args) + + optional = set() + if defaults: + optional |= set(flaglist[-len(defaults):]) + return flaglist, optional + +def factory_setup(name, obj): + """Prepare a factory for use by oe.types""" + args, optional = get_callable_args(obj) + extra_args = args[1:] + if extra_args: + obj.flags, optional = extra_args, optional + obj.optflags = set(optional) + else: + obj.flags = obj.optflags = () + + if not hasattr(obj, 'name'): + obj.name = name + +def register(name, factory): + factory_setup(name, factory) + types[factory.name] = factory + +# Set the 'flags' and 'optflags' attributes of all our types +for name in dir(_types): + if name.startswith('_'): + continue + + obj = getattr(_types, name) + if not callable(obj): + continue + + register(name, obj) |