Implement variable typing (sync from OE)
This implementation consists of two components: - Type creation python modules, 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.data for pkg in oe.data.typed_value("PACKAGES", d): bb.note("package: %s" % pkg) } LIBTOOL_HAS_SYSROOT = "yes" LIBTOOL_HAS_SYSROOT[type] = "boolean" python () { import oe.data assert(oe.data.typed_value("LIBTOOL_HAS_SYSROOT", d) == True) } (From OE-Core rev: a04ce490e933fc7534db33f635b025c25329c564) Signed-off-by: Chris Larson <chris_larson@mentor.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
parent
4f5209ce31
commit
e4921fda5b
|
@ -28,6 +28,7 @@ python sys_path_eh () {
|
|||
|
||||
import oe.path
|
||||
import oe.utils
|
||||
import oe.data
|
||||
inject("bb", bb)
|
||||
inject("sys", sys)
|
||||
inject("time", time)
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# Check types of bitbake configuration variables
|
||||
#
|
||||
# See oe.types for details.
|
||||
|
||||
python check_types() {
|
||||
import oe.types
|
||||
if isinstance(e, bb.event.ConfigParsed):
|
||||
for key in e.data.keys():
|
||||
if e.data.getVarFlag(key, "type"):
|
||||
oe.types.value(key, e.data)
|
||||
}
|
||||
addhandler check_types
|
|
@ -0,0 +1,13 @@
|
|||
import oe.maketype
|
||||
import bb.msg
|
||||
|
||||
def typed_value(key, d):
|
||||
"""Construct a value for the specified metadata variable, using its flags
|
||||
to determine the type and parameters for construction."""
|
||||
var_type = d.getVarFlag(key, 'type')
|
||||
flags = d.getVarFlags(key)
|
||||
|
||||
try:
|
||||
return oe.maketype.create(d.getVar(key, True) or '', var_type, **flags)
|
||||
except (TypeError, ValueError), exc:
|
||||
bb.msg.fatal(bb.msg.domain.Data, "%s: %s" % (key, str(exc)))
|
|
@ -0,0 +1,100 @@
|
|||
"""OpenEmbedded variable typing support
|
||||
|
||||
Types are defined in the metadata by name, using the 'type' flag on a
|
||||
variable. Other flags may be utilized in the construction of the types. See
|
||||
the arguments of the type's factory for details.
|
||||
"""
|
||||
|
||||
import bb
|
||||
import inspect
|
||||
import types
|
||||
|
||||
available_types = {}
|
||||
|
||||
class MissingFlag(TypeError):
|
||||
"""A particular flag is required to construct the type, but has not been
|
||||
provided."""
|
||||
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):
|
||||
"""Return the factory for a specified type."""
|
||||
if var_type is None:
|
||||
raise TypeError("No type specified. Valid types: %s" %
|
||||
', '.join(available_types))
|
||||
try:
|
||||
return available_types[var_type]
|
||||
except KeyError:
|
||||
raise TypeError("Invalid type '%s':\n Valid types: %s" %
|
||||
(var_type, ', '.join(available_types)))
|
||||
|
||||
def create(value, var_type, **flags):
|
||||
"""Create an object of the specified type, given the specified flags and
|
||||
string value."""
|
||||
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 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."""
|
||||
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):
|
||||
"""Register a type, given its name and a factory callable.
|
||||
|
||||
Determines the required and optional flags from the factory's
|
||||
arguments."""
|
||||
factory_setup(name, factory)
|
||||
available_types[factory.name] = factory
|
||||
|
||||
|
||||
# Register all our included types
|
||||
for name in dir(types):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
|
||||
obj = getattr(types, name)
|
||||
if not callable(obj):
|
||||
continue
|
||||
|
||||
register(name, obj)
|
|
@ -0,0 +1,62 @@
|
|||
import unittest
|
||||
from oe.maketype 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):
|
||||
cls = factory(type)
|
||||
self.assertIsNot(cls, None)
|
||||
self.assertIsInstance(create(value, type, **flags), cls)
|
||||
|
||||
class TestBooleanType(TestTypes):
|
||||
def test_invalid(self):
|
||||
self.assertRaises(ValueError, create, '', 'boolean')
|
||||
self.assertRaises(ValueError, create, 'foo', 'boolean')
|
||||
self.assertRaises(TypeError, create, object(), 'boolean')
|
||||
|
||||
def test_true(self):
|
||||
self.assertTrue(create('y', 'boolean'))
|
||||
self.assertTrue(create('yes', 'boolean'))
|
||||
self.assertTrue(create('1', 'boolean'))
|
||||
self.assertTrue(create('t', 'boolean'))
|
||||
self.assertTrue(create('true', 'boolean'))
|
||||
self.assertTrue(create('TRUE', 'boolean'))
|
||||
self.assertTrue(create('truE', 'boolean'))
|
||||
|
||||
def test_false(self):
|
||||
self.assertFalse(create('n', 'boolean'))
|
||||
self.assertFalse(create('no', 'boolean'))
|
||||
self.assertFalse(create('0', 'boolean'))
|
||||
self.assertFalse(create('f', 'boolean'))
|
||||
self.assertFalse(create('false', 'boolean'))
|
||||
self.assertFalse(create('FALSE', 'boolean'))
|
||||
self.assertFalse(create('faLse', 'boolean'))
|
||||
|
||||
def test_bool_equality(self):
|
||||
self.assertEqual(create('n', 'boolean'), False)
|
||||
self.assertNotEqual(create('n', 'boolean'), True)
|
||||
self.assertEqual(create('y', 'boolean'), True)
|
||||
self.assertNotEqual(create('y', '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'], ':')
|
|
@ -0,0 +1,104 @@
|
|||
import re
|
||||
|
||||
class OEList(list):
|
||||
"""OpenEmbedded 'list' type
|
||||
|
||||
Acts as an ordinary list, but is constructed from a string value and a
|
||||
separator (optional), and re-joins itself when converted to a string with
|
||||
str(). Set the variable type flag to 'list' to use this type, and the
|
||||
'separator' flag may be specified (defaulting to whitespace)."""
|
||||
|
||||
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):
|
||||
"""OpenEmbedded 'choice' type
|
||||
|
||||
Acts as a multiple choice for the user. To use this, set the variable
|
||||
type flag to 'choice', and set the 'choices' flag to a space separated
|
||||
list of valid values."""
|
||||
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):
|
||||
"""OpenEmbedded 'regex' type
|
||||
|
||||
Acts as a regular expression, returning the pre-compiled regular
|
||||
expression pattern object. To use this type, set the variable type flag
|
||||
to 'regex', and optionally, set the 'regexflags' type to a space separated
|
||||
list of the flags to control the regular expression matching (e.g.
|
||||
FOO[regexflags] += 'ignorecase'). See the python documentation on the
|
||||
're' module for a list of valid flags."""
|
||||
|
||||
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):
|
||||
"""OpenEmbedded 'boolean' type
|
||||
|
||||
Valid values for true: 'yes', 'y', 'true', 't', '1'
|
||||
Valid values for false: 'no', 'n', 'false', 'f', '0'
|
||||
"""
|
||||
|
||||
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, numberbase=10):
|
||||
"""OpenEmbedded 'integer' type
|
||||
|
||||
Defaults to base 10, but this can be specified using the optional
|
||||
'numberbase' flag."""
|
||||
|
||||
return int(value, int(numberbase))
|
||||
|
||||
_float = float
|
||||
def float(value, fromhex='false'):
|
||||
"""OpenEmbedded floating point type
|
||||
|
||||
To use this type, set the type flag to 'float', and optionally set the
|
||||
'fromhex' flag to a true value (obeying the same rules as for the
|
||||
'boolean' type) if the value is in base 16 rather than base 10."""
|
||||
|
||||
if boolean(fromhex):
|
||||
return _float.fromhex(value)
|
||||
else:
|
||||
return _float(value)
|
Loading…
Reference in New Issue