1
0
mirror of https://github.com/ARM-software/devlib.git synced 2025-02-24 21:47:50 +00:00

target: Introduce TypedKernelConfig

Maps Kconfig types to appropriate Python types, and act as a regular
mapping with extended API:
    * tristate and bool values mapped to an Enum
    * int values to int
    * hex values to HexInt subclass of int that defaults to parsing and
      printing in hex format.

Implement KernelConfig as a shim on top of TypedKernelConfig so they
share most of the code. Code needing a TypedKernelConfig from a
KernelConfig producer such as Target.config can trivially access the
`typed_config` attribute of KernelConfig objects.
This commit is contained in:
Douglas RAILLARD 2018-12-17 11:53:52 +00:00 committed by Marc Bonnici
parent 5b51c2644e
commit f65130b7c7

@ -30,6 +30,14 @@ import xml.dom.minidom
import copy
from collections import namedtuple, defaultdict
from pipes import quote
from past.types import basestring
from numbers import Number
try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping
from enum import Enum
from devlib.host import LocalConnection, PACKAGE_BIN_DIRECTORY
from devlib.module import get_module
@ -1763,8 +1771,56 @@ class KernelVersion(object):
__repr__ = __str__
class KernelConfig(object):
class HexInt(int):
"""
Subclass of :class:`int` that uses hexadecimal formatting by default.
"""
def __new__(cls, val=0, base=16):
super_new = super(HexInt, cls).__new__
if isinstance(val, Number):
return super_new(cls, val)
else:
return super_new(cls, val, base=base)
def __str__(self):
return hex(self)
class KernelConfigTristate(Enum):
YES = 'y'
NO = 'n'
MODULE = 'm'
def __bool__(self):
"""
Allow using this enum to represent bool Kconfig type, although it is
technically different from tristate.
"""
return self in (self.YES, self.MODULE)
def __nonzero__(self):
"""
For Python 2.x compatibility.
"""
return self.__bool__()
@classmethod
def from_str(cls, str_):
for state in cls:
if state.value == str_:
return state
raise ValueError('No kernel config tristate value matches "{}"'.format(str_))
class TypedKernelConfig(Mapping):
"""
Mapping-like typed version of :class:`KernelConfig`.
Values are either :class:`str`, :class:`int`,
:class:`KernelConfigTristate`, or :class:`HexInt`. ``hex`` Kconfig type is
mapped to :class:`HexInt` and ``bool`` to :class:`KernelConfigTristate`.
"""
not_set_regex = re.compile(r'# (\S+) is not set')
@staticmethod
@ -1774,17 +1830,93 @@ class KernelConfig(object):
name = 'CONFIG_' + name
return name
def iteritems(self):
return iter(self._config.items())
def __init__(self, mapping=None):
mapping = mapping if mapping is not None else {}
self._config = {
# Ensure we use the canonical name of the config keys for internal
# representation
self.get_config_name(k): v
for k, v in dict(mapping).items()
}
def __init__(self, text):
self.text = text
self._config = self._parse_text(text)
@classmethod
def from_str(cls, text):
"""
Build a :class:`TypedKernelConfig` out of the string content of a
Kconfig file.
"""
return cls(cls._parse_text(text))
@staticmethod
def _val_to_str(val):
"Convert back values to Kconfig-style string value"
# Special case the gracefully handle the output of get()
if val is None:
return None
elif isinstance(val, KernelConfigTristate):
return val.value
elif isinstance(val, basestring):
return '"{}"'.format(val)
else:
return str(val)
def __str__(self):
return '\n'.join(
'{}={}'.format(k, self._val_to_str(v))
for k, v in self.items()
)
@staticmethod
def _parse_val(k, v):
"""
Parse a value of types handled by Kconfig:
* string
* bool
* tristate
* hex
* int
Since bool cannot be distinguished from tristate, tristate is
always used. :meth:`KernelConfigTristate.__bool__` will allow using
it as a bool though, so it should not impact user code.
"""
if not v:
return None
# Handle "string" type
if v.startswith('"'):
# Strip enclosing "
return v[1:-1]
else:
try:
# Handles "bool" and "tristate" types
return KernelConfigTristate.from_str(v)
except ValueError:
pass
try:
# Handles "int" type
return int(v)
except ValueError:
pass
try:
# Handles "hex" type
return HexInt(v)
except ValueError:
pass
# If no type could be parsed
raise ValueError('Could not parse Kconfig key: {}={}'.format(
k, v
), k, v
)
@classmethod
def _parse_text(cls, text):
config = {}
for line in text.split('\n'):
for line in text.splitlines():
line = line.strip()
# skip empty lines
@ -1802,40 +1934,96 @@ class KernelConfig(object):
name, value = line.split('=', 1)
name = cls.get_config_name(name.strip())
config[name] = value.strip()
value = cls._parse_val(name, value.strip())
config[name] = value
return config
def get(self, name, strict=False):
def __getitem__(self, name):
name = self.get_config_name(name)
res = self._config.get(name)
if not res and strict:
try:
return self._config[name]
except KeyError:
raise KernelConfigKeyError(
"{} is not exposed in kernel config".format(name),
name
)
return self._config.get(name)
def __iter__(self):
return iter(self._config)
def __len__(self):
return len(self._config)
def __contains__(self, name):
name = self.get_config_name(name)
return name in self._config
def like(self, name):
regex = re.compile(name, re.I)
result = {}
for k, v in self._config.items():
if regex.search(k):
result[k] = v
return result
return {
k: v for k, v in self.items()
if regex.search(k)
}
def is_enabled(self, name):
return self.get(name) == 'y'
return self.get(name) is KernelConfigTristate.YES
def is_module(self, name):
return self.get(name) == 'm'
return self.get(name) is KernelConfigTristate.MODULE
def is_not_set(self, name):
return self.get(name) == 'n'
return self.get(name) is KernelConfigTristate.NO
def has(self, name):
return self.get(name) in ['m', 'y']
return self.is_enabled(name) or self.is_module(name)
class KernelConfig(object):
"""
Backward compatibility shim on top of :class:`TypedKernelConfig`.
This class does not provide a Mapping API and only return string values.
"""
def __init__(self, text):
# Expose typed_config as a non-private attribute, so that user code
# needing it can get it from any existing producer of KernelConfig.
self.typed_config = TypedKernelConfig.from_str(text)
# Expose the original text for backward compatibility
self.text = text
get_config_name = TypedKernelConfig.get_config_name
not_set_regex = TypedKernelConfig.not_set_regex
def iteritems(self):
for k, v in self.typed_config.items():
yield (k, self.typed_config._val_to_str(v))
def get(self, name, strict=False):
if strict:
val = self.typed_config[name]
else:
val = self.typed_config.get(name)
return self.typed_config._val_to_str(val)
def like(self, name):
return {
k: self.typed_config._val_to_str(v)
for k, v in self.typed_config.like(name).items()
}
def is_enabled(self, name):
return self.typed_config.is_enabled(name)
def is_module(self, name):
return self.typed_config.is_module(name)
def is_not_set(self, name):
return self.typed_config.is_not_set(name)
def has(self, name):
return self.typed_config.has(name)
class LocalLinuxTarget(LinuxTarget):