1
0
mirror of https://github.com/ARM-software/devlib.git synced 2025-02-25 05:57:51 +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

View File

@ -30,6 +30,14 @@ import xml.dom.minidom
import copy import copy
from collections import namedtuple, defaultdict from collections import namedtuple, defaultdict
from pipes import quote 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.host import LocalConnection, PACKAGE_BIN_DIRECTORY
from devlib.module import get_module from devlib.module import get_module
@ -1763,8 +1771,56 @@ class KernelVersion(object):
__repr__ = __str__ __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') not_set_regex = re.compile(r'# (\S+) is not set')
@staticmethod @staticmethod
@ -1774,17 +1830,93 @@ class KernelConfig(object):
name = 'CONFIG_' + name name = 'CONFIG_' + name
return name return name
def iteritems(self): def __init__(self, mapping=None):
return iter(self._config.items()) 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): @classmethod
self.text = text def from_str(cls, text):
self._config = self._parse_text(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 @classmethod
def _parse_text(cls, text): def _parse_text(cls, text):
config = {} config = {}
for line in text.split('\n'): for line in text.splitlines():
line = line.strip() line = line.strip()
# skip empty lines # skip empty lines
@ -1802,40 +1934,96 @@ class KernelConfig(object):
name, value = line.split('=', 1) name, value = line.split('=', 1)
name = cls.get_config_name(name.strip()) name = cls.get_config_name(name.strip())
config[name] = value.strip() value = cls._parse_val(name, value.strip())
config[name] = value
return config return config
def get(self, name, strict=False): def __getitem__(self, name):
name = self.get_config_name(name) name = self.get_config_name(name)
res = self._config.get(name) try:
return self._config[name]
if not res and strict: except KeyError:
raise KernelConfigKeyError( raise KernelConfigKeyError(
"{} is not exposed in kernel config".format(name), "{} is not exposed in kernel config".format(name),
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): def like(self, name):
regex = re.compile(name, re.I) regex = re.compile(name, re.I)
result = {} return {
for k, v in self._config.items(): k: v for k, v in self.items()
if regex.search(k): if regex.search(k)
result[k] = v }
return result
def is_enabled(self, name): def is_enabled(self, name):
return self.get(name) == 'y' return self.get(name) is KernelConfigTristate.YES
def is_module(self, name): def is_module(self, name):
return self.get(name) == 'm' return self.get(name) is KernelConfigTristate.MODULE
def is_not_set(self, name): def is_not_set(self, name):
return self.get(name) == 'n' return self.get(name) is KernelConfigTristate.NO
def has(self, name): 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): class LocalLinuxTarget(LinuxTarget):