summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Larson <chris_larson@mentor.com>2010-11-09 21:48:13 (GMT)
committerRichard Purdie <richard.purdie@linuxfoundation.org>2011-05-20 16:34:22 (GMT)
commite4921fda5b3a18c797acd267f2b1179ac032cd42 (patch)
treeb0a0df97faac10cbc8008e3db55cd81235b76379
parent4f5209ce31f1cc7fc0c97453b5247640c07e7f31 (diff)
downloadpoky-e4921fda5b3a18c797acd267f2b1179ac032cd42.tar.gz
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>
-rw-r--r--meta/classes/base.bbclass1
-rw-r--r--meta/classes/typecheck.bbclass12
-rw-r--r--meta/lib/oe/data.py13
-rw-r--r--meta/lib/oe/maketype.py100
-rw-r--r--meta/lib/oe/test_types.py62
-rw-r--r--meta/lib/oe/types.py104
6 files changed, 292 insertions, 0 deletions
diff --git a/meta/classes/base.bbclass b/meta/classes/base.bbclass
index 0c7f8fc..5ccc553 100644
--- a/meta/classes/base.bbclass
+++ b/meta/classes/base.bbclass
@@ -28,6 +28,7 @@ python sys_path_eh () {
28 28
29 import oe.path 29 import oe.path
30 import oe.utils 30 import oe.utils
31 import oe.data
31 inject("bb", bb) 32 inject("bb", bb)
32 inject("sys", sys) 33 inject("sys", sys)
33 inject("time", time) 34 inject("time", time)
diff --git a/meta/classes/typecheck.bbclass b/meta/classes/typecheck.bbclass
new file mode 100644
index 0000000..646cd4e
--- /dev/null
+++ b/meta/classes/typecheck.bbclass
@@ -0,0 +1,12 @@
1# Check types of bitbake configuration variables
2#
3# See oe.types for details.
4
5python check_types() {
6 import oe.types
7 if isinstance(e, bb.event.ConfigParsed):
8 for key in e.data.keys():
9 if e.data.getVarFlag(key, "type"):
10 oe.types.value(key, e.data)
11}
12addhandler check_types
diff --git a/meta/lib/oe/data.py b/meta/lib/oe/data.py
new file mode 100644
index 0000000..8b7c3cd
--- /dev/null
+++ b/meta/lib/oe/data.py
@@ -0,0 +1,13 @@
1import oe.maketype
2import bb.msg
3
4def typed_value(key, d):
5 """Construct a value for the specified metadata variable, using its flags
6 to determine the type and parameters for construction."""
7 var_type = d.getVarFlag(key, 'type')
8 flags = d.getVarFlags(key)
9
10 try:
11 return oe.maketype.create(d.getVar(key, True) or '', var_type, **flags)
12 except (TypeError, ValueError), exc:
13 bb.msg.fatal(bb.msg.domain.Data, "%s: %s" % (key, str(exc)))
diff --git a/meta/lib/oe/maketype.py b/meta/lib/oe/maketype.py
new file mode 100644
index 0000000..0e9dbc6
--- /dev/null
+++ b/meta/lib/oe/maketype.py
@@ -0,0 +1,100 @@
1"""OpenEmbedded variable typing support
2
3Types are defined in the metadata by name, using the 'type' flag on a
4variable. Other flags may be utilized in the construction of the types. See
5the arguments of the type's factory for details.
6"""
7
8import bb
9import inspect
10import types
11
12available_types = {}
13
14class MissingFlag(TypeError):
15 """A particular flag is required to construct the type, but has not been
16 provided."""
17 def __init__(self, flag, type):
18 self.flag = flag
19 self.type = type
20 TypeError.__init__(self)
21
22 def __str__(self):
23 return "Type '%s' requires flag '%s'" % (self.type, self.flag)
24
25def factory(var_type):
26 """Return the factory for a specified type."""
27 if var_type is None:
28 raise TypeError("No type specified. Valid types: %s" %
29 ', '.join(available_types))
30 try:
31 return available_types[var_type]
32 except KeyError:
33 raise TypeError("Invalid type '%s':\n Valid types: %s" %
34 (var_type, ', '.join(available_types)))
35
36def create(value, var_type, **flags):
37 """Create an object of the specified type, given the specified flags and
38 string value."""
39 obj = factory(var_type)
40 objflags = {}
41 for flag in obj.flags:
42 if flag not in flags:
43 if flag not in obj.optflags:
44 raise MissingFlag(flag, var_type)
45 else:
46 objflags[flag] = flags[flag]
47
48 return obj(value, **objflags)
49
50def get_callable_args(obj):
51 """Grab all but the first argument of the specified callable, returning
52 the list, as well as a list of which of the arguments have default
53 values."""
54 if type(obj) is type:
55 obj = obj.__init__
56
57 args, varargs, keywords, defaults = inspect.getargspec(obj)
58 flaglist = []
59 if args:
60 if len(args) > 1 and args[0] == 'self':
61 args = args[1:]
62 flaglist.extend(args)
63
64 optional = set()
65 if defaults:
66 optional |= set(flaglist[-len(defaults):])
67 return flaglist, optional
68
69def factory_setup(name, obj):
70 """Prepare a factory for use."""
71 args, optional = get_callable_args(obj)
72 extra_args = args[1:]
73 if extra_args:
74 obj.flags, optional = extra_args, optional
75 obj.optflags = set(optional)
76 else:
77 obj.flags = obj.optflags = ()
78
79 if not hasattr(obj, 'name'):
80 obj.name = name
81
82def register(name, factory):
83 """Register a type, given its name and a factory callable.
84
85 Determines the required and optional flags from the factory's
86 arguments."""
87 factory_setup(name, factory)
88 available_types[factory.name] = factory
89
90
91# Register all our included types
92for name in dir(types):
93 if name.startswith('_'):
94 continue
95
96 obj = getattr(types, name)
97 if not callable(obj):
98 continue
99
100 register(name, obj)
diff --git a/meta/lib/oe/test_types.py b/meta/lib/oe/test_types.py
new file mode 100644
index 0000000..367cc30
--- /dev/null
+++ b/meta/lib/oe/test_types.py
@@ -0,0 +1,62 @@
1import unittest
2from oe.maketype import create, factory
3
4class TestTypes(unittest.TestCase):
5 def assertIsInstance(self, obj, cls):
6 return self.assertTrue(isinstance(obj, cls))
7
8 def assertIsNot(self, obj, other):
9 return self.assertFalse(obj is other)
10
11 def assertFactoryCreated(self, value, type, **flags):
12 cls = factory(type)
13 self.assertIsNot(cls, None)
14 self.assertIsInstance(create(value, type, **flags), cls)
15
16class TestBooleanType(TestTypes):
17 def test_invalid(self):
18 self.assertRaises(ValueError, create, '', 'boolean')
19 self.assertRaises(ValueError, create, 'foo', 'boolean')
20 self.assertRaises(TypeError, create, object(), 'boolean')
21
22 def test_true(self):
23 self.assertTrue(create('y', 'boolean'))
24 self.assertTrue(create('yes', 'boolean'))
25 self.assertTrue(create('1', 'boolean'))
26 self.assertTrue(create('t', 'boolean'))
27 self.assertTrue(create('true', 'boolean'))
28 self.assertTrue(create('TRUE', 'boolean'))
29 self.assertTrue(create('truE', 'boolean'))
30
31 def test_false(self):
32 self.assertFalse(create('n', 'boolean'))
33 self.assertFalse(create('no', 'boolean'))
34 self.assertFalse(create('0', 'boolean'))
35 self.assertFalse(create('f', 'boolean'))
36 self.assertFalse(create('false', 'boolean'))
37 self.assertFalse(create('FALSE', 'boolean'))
38 self.assertFalse(create('faLse', 'boolean'))
39
40 def test_bool_equality(self):
41 self.assertEqual(create('n', 'boolean'), False)
42 self.assertNotEqual(create('n', 'boolean'), True)
43 self.assertEqual(create('y', 'boolean'), True)
44 self.assertNotEqual(create('y', 'boolean'), False)
45
46class TestList(TestTypes):
47 def assertListEqual(self, value, valid, sep=None):
48 obj = create(value, 'list', separator=sep)
49 self.assertEqual(obj, valid)
50 if sep is not None:
51 self.assertEqual(obj.separator, sep)
52 self.assertEqual(str(obj), obj.separator.join(obj))
53
54 def test_list_nosep(self):
55 testlist = ['alpha', 'beta', 'theta']
56 self.assertListEqual('alpha beta theta', testlist)
57 self.assertListEqual('alpha beta\ttheta', testlist)
58 self.assertListEqual('alpha', ['alpha'])
59
60 def test_list_usersep(self):
61 self.assertListEqual('foo:bar', ['foo', 'bar'], ':')
62 self.assertListEqual('foo:bar:baz', ['foo', 'bar', 'baz'], ':')
diff --git a/meta/lib/oe/types.py b/meta/lib/oe/types.py
new file mode 100644
index 0000000..ea31cf4
--- /dev/null
+++ b/meta/lib/oe/types.py
@@ -0,0 +1,104 @@
1import re
2
3class OEList(list):
4 """OpenEmbedded 'list' type
5
6 Acts as an ordinary list, but is constructed from a string value and a
7 separator (optional), and re-joins itself when converted to a string with
8 str(). Set the variable type flag to 'list' to use this type, and the
9 'separator' flag may be specified (defaulting to whitespace)."""
10
11 name = "list"
12
13 def __init__(self, value, separator = None):
14 if value is not None:
15 list.__init__(self, value.split(separator))
16 else:
17 list.__init__(self)
18
19 if separator is None:
20 self.separator = " "
21 else:
22 self.separator = separator
23
24 def __str__(self):
25 return self.separator.join(self)
26
27def choice(value, choices):
28 """OpenEmbedded 'choice' type
29
30 Acts as a multiple choice for the user. To use this, set the variable
31 type flag to 'choice', and set the 'choices' flag to a space separated
32 list of valid values."""
33 if not isinstance(value, basestring):
34 raise TypeError("choice accepts a string, not '%s'" % type(value))
35
36 value = value.lower()
37 choices = choices.lower()
38 if value not in choices.split():
39 raise ValueError("Invalid choice '%s'. Valid choices: %s" %
40 (value, choices))
41 return value
42
43def regex(value, regexflags=None):
44 """OpenEmbedded 'regex' type
45
46 Acts as a regular expression, returning the pre-compiled regular
47 expression pattern object. To use this type, set the variable type flag
48 to 'regex', and optionally, set the 'regexflags' type to a space separated
49 list of the flags to control the regular expression matching (e.g.
50 FOO[regexflags] += 'ignorecase'). See the python documentation on the
51 're' module for a list of valid flags."""
52
53 flagval = 0
54 if regexflags:
55 for flag in regexflags.split():
56 flag = flag.upper()
57 try:
58 flagval |= getattr(re, flag)
59 except AttributeError:
60 raise ValueError("Invalid regex flag '%s'" % flag)
61
62 try:
63 return re.compile(value, flagval)
64 except re.error, exc:
65 raise ValueError("Invalid regex value '%s': %s" %
66 (value, exc.args[0]))
67
68def boolean(value):
69 """OpenEmbedded 'boolean' type
70
71 Valid values for true: 'yes', 'y', 'true', 't', '1'
72 Valid values for false: 'no', 'n', 'false', 'f', '0'
73 """
74
75 if not isinstance(value, basestring):
76 raise TypeError("boolean accepts a string, not '%s'" % type(value))
77
78 value = value.lower()
79 if value in ('yes', 'y', 'true', 't', '1'):
80 return True
81 elif value in ('no', 'n', 'false', 'f', '0'):
82 return False
83 raise ValueError("Invalid boolean value '%s'" % value)
84
85def integer(value, numberbase=10):
86 """OpenEmbedded 'integer' type
87
88 Defaults to base 10, but this can be specified using the optional
89 'numberbase' flag."""
90
91 return int(value, int(numberbase))
92
93_float = float
94def float(value, fromhex='false'):
95 """OpenEmbedded floating point type
96
97 To use this type, set the type flag to 'float', and optionally set the
98 'fromhex' flag to a true value (obeying the same rules as for the
99 'boolean' type) if the value is in base 16 rather than base 10."""
100
101 if boolean(fromhex):
102 return _float.fromhex(value)
103 else:
104 return _float(value)