2019-09-23 17:02:43 +08:00
|
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
|
|
#
|
|
|
|
# Builds a .config from a kunitconfig.
|
|
|
|
#
|
|
|
|
# Copyright (C) 2019, Google LLC.
|
|
|
|
# Author: Felix Guo <felixguoxiuping@gmail.com>
|
|
|
|
# Author: Brendan Higgins <brendanhiggins@google.com>
|
|
|
|
|
2022-01-19 03:09:21 +08:00
|
|
|
from dataclasses import dataclass
|
2019-09-23 17:02:43 +08:00
|
|
|
import re
|
2023-03-17 06:06:38 +08:00
|
|
|
from typing import Any, Dict, Iterable, List, Tuple
|
2019-09-23 17:02:43 +08:00
|
|
|
|
2020-03-24 10:43:33 +08:00
|
|
|
CONFIG_IS_NOT_SET_PATTERN = r'^# CONFIG_(\w+) is not set$'
|
2020-06-08 05:57:15 +08:00
|
|
|
CONFIG_PATTERN = r'^CONFIG_(\w+)=(\S+|".*")$'
|
2019-09-23 17:02:43 +08:00
|
|
|
|
2022-01-19 03:09:21 +08:00
|
|
|
@dataclass(frozen=True)
|
|
|
|
class KconfigEntry:
|
|
|
|
name: str
|
|
|
|
value: str
|
2019-09-23 17:02:43 +08:00
|
|
|
|
|
|
|
def __str__(self) -> str:
|
2020-03-24 10:43:33 +08:00
|
|
|
if self.value == 'n':
|
2022-05-10 04:49:09 +08:00
|
|
|
return f'# CONFIG_{self.name} is not set'
|
|
|
|
return f'CONFIG_{self.name}={self.value}'
|
2019-09-23 17:02:43 +08:00
|
|
|
|
|
|
|
|
|
|
|
class KconfigParseError(Exception):
|
|
|
|
"""Error parsing Kconfig defconfig or .config."""
|
|
|
|
|
|
|
|
|
2022-05-10 04:49:09 +08:00
|
|
|
class Kconfig:
|
2019-09-23 17:02:43 +08:00
|
|
|
"""Represents defconfig or .config specified using the Kconfig language."""
|
|
|
|
|
2021-01-15 08:39:11 +08:00
|
|
|
def __init__(self) -> None:
|
2022-06-28 06:14:44 +08:00
|
|
|
self._entries = {} # type: Dict[str, str]
|
2019-09-23 17:02:43 +08:00
|
|
|
|
2023-03-17 06:06:38 +08:00
|
|
|
def __eq__(self, other: Any) -> bool:
|
2022-06-28 06:14:44 +08:00
|
|
|
if not isinstance(other, self.__class__):
|
|
|
|
return False
|
|
|
|
return self._entries == other._entries
|
2019-09-23 17:02:43 +08:00
|
|
|
|
2022-06-28 06:14:44 +08:00
|
|
|
def __repr__(self) -> str:
|
|
|
|
return ','.join(str(e) for e in self.as_entries())
|
|
|
|
|
|
|
|
def as_entries(self) -> Iterable[KconfigEntry]:
|
|
|
|
for name, value in self._entries.items():
|
|
|
|
yield KconfigEntry(name, value)
|
|
|
|
|
|
|
|
def add_entry(self, name: str, value: str) -> None:
|
|
|
|
self._entries[name] = value
|
2019-09-23 17:02:43 +08:00
|
|
|
|
|
|
|
def is_subset_of(self, other: 'Kconfig') -> bool:
|
2022-06-28 06:14:44 +08:00
|
|
|
for name, value in self._entries.items():
|
|
|
|
b = other._entries.get(name)
|
2020-12-09 07:21:02 +08:00
|
|
|
if b is None:
|
2022-06-28 06:14:44 +08:00
|
|
|
if value == 'n':
|
2020-03-24 10:43:33 +08:00
|
|
|
continue
|
2020-12-09 07:21:02 +08:00
|
|
|
return False
|
2022-06-28 06:14:44 +08:00
|
|
|
if value != b:
|
2020-03-24 10:43:33 +08:00
|
|
|
return False
|
|
|
|
return True
|
2019-09-23 17:02:43 +08:00
|
|
|
|
kunit: tool: make --kunitconfig repeatable, blindly concat
It's come up a few times that it would be useful to have --kunitconfig
be repeatable [1][2].
This could be done before with a bit of shell-fu, e.g.
$ find fs/ -name '.kunitconfig' -exec cat {} + | \
./tools/testing/kunit/kunit.py run --kunitconfig=/dev/stdin
or equivalently:
$ cat fs/ext4/.kunitconfig fs/fat/.kunitconfig | \
./tools/testing/kunit/kunit.py run --kunitconfig=/dev/stdin
But this can be fairly clunky to use in practice.
And having explicit support in kunit.py opens the door to having more
config fragments of interest, e.g. options for PCI on UML [1], UML
coverage [2], variants of tests [3].
There's another argument to be made that users can just use multiple
--kconfig_add's, but this gets very clunky very fast (e.g. [2]).
Note: there's a big caveat here that some kconfig options might be
incompatible. We try to give a clearish error message in the simple case
where the same option appears multiple times with conflicting values,
but more subtle ones (e.g. mutually exclusive options) will be
potentially very confusing for the user. I don't know we can do better.
Note 2: if you want to combine a --kunitconfig with the default, you
either have to do to specify the current build_dir
> --kunitconfig=.kunit --kunitconfig=additional.config
or
> --kunitconfig=tools/testing/kunit/configs/default.config --kunitconifg=additional.config
each of which have their downsides (former depends on --build_dir,
doesn't work if you don't have a .kunitconfig yet), etc.
Example with conflicting values:
> $ ./tools/testing/kunit/kunit.py config --kunitconfig=lib/kunit --kunitconfig=/dev/stdin <<EOF
> CONFIG_KUNIT_TEST=n
> CONFIG_KUNIT=m
> EOF
> ...
> kunit_kernel.ConfigError: Multiple values specified for 2 options in kunitconfig:
> CONFIG_KUNIT=y
> vs from /dev/stdin
> CONFIG_KUNIT=m
>
> CONFIG_KUNIT_TEST=y
> vs from /dev/stdin
> # CONFIG_KUNIT_TEST is not set
[1] https://lists.freedesktop.org/archives/dri-devel/2022-June/357616.html
[2] https://lore.kernel.org/linux-kselftest/CAFd5g45f3X3xF2vz2BkTHRqOC4uW6GZxtUUMaP5mwwbK8uNVtA@mail.gmail.com/
[3] https://lore.kernel.org/linux-kselftest/CANpmjNOdSy6DuO6CYZ4UxhGxqhjzx4tn0sJMbRqo2xRFv9kX6Q@mail.gmail.com/
Signed-off-by: Daniel Latypov <dlatypov@google.com>
Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
2022-07-08 09:36:32 +08:00
|
|
|
def conflicting_options(self, other: 'Kconfig') -> List[Tuple[KconfigEntry, KconfigEntry]]:
|
|
|
|
diff = [] # type: List[Tuple[KconfigEntry, KconfigEntry]]
|
|
|
|
for name, value in self._entries.items():
|
|
|
|
b = other._entries.get(name)
|
|
|
|
if b and value != b:
|
|
|
|
pair = (KconfigEntry(name, value), KconfigEntry(name, b))
|
|
|
|
diff.append(pair)
|
|
|
|
return diff
|
|
|
|
|
2021-05-27 05:24:06 +08:00
|
|
|
def merge_in_entries(self, other: 'Kconfig') -> None:
|
2022-06-28 06:14:44 +08:00
|
|
|
for name, value in other._entries.items():
|
|
|
|
self._entries[name] = value
|
2021-05-27 05:24:06 +08:00
|
|
|
|
2019-09-23 17:02:43 +08:00
|
|
|
def write_to_file(self, path: str) -> None:
|
2021-05-27 05:24:06 +08:00
|
|
|
with open(path, 'a+') as f:
|
2022-06-28 06:14:44 +08:00
|
|
|
for e in self.as_entries():
|
|
|
|
f.write(str(e) + '\n')
|
2019-09-23 17:02:43 +08:00
|
|
|
|
2021-11-06 09:30:57 +08:00
|
|
|
def parse_file(path: str) -> Kconfig:
|
|
|
|
with open(path, 'r') as f:
|
|
|
|
return parse_from_string(f.read())
|
|
|
|
|
|
|
|
def parse_from_string(blob: str) -> Kconfig:
|
|
|
|
"""Parses a string containing Kconfig entries."""
|
|
|
|
kconfig = Kconfig()
|
|
|
|
is_not_set_matcher = re.compile(CONFIG_IS_NOT_SET_PATTERN)
|
|
|
|
config_matcher = re.compile(CONFIG_PATTERN)
|
|
|
|
for line in blob.split('\n'):
|
|
|
|
line = line.strip()
|
|
|
|
if not line:
|
|
|
|
continue
|
|
|
|
|
|
|
|
match = config_matcher.match(line)
|
|
|
|
if match:
|
2022-06-28 06:14:44 +08:00
|
|
|
kconfig.add_entry(match.group(1), match.group(2))
|
2021-11-06 09:30:57 +08:00
|
|
|
continue
|
|
|
|
|
|
|
|
empty_match = is_not_set_matcher.match(line)
|
|
|
|
if empty_match:
|
2022-06-28 06:14:44 +08:00
|
|
|
kconfig.add_entry(empty_match.group(1), 'n')
|
2021-11-06 09:30:57 +08:00
|
|
|
continue
|
|
|
|
|
|
|
|
if line[0] == '#':
|
|
|
|
continue
|
2022-05-10 04:49:09 +08:00
|
|
|
raise KconfigParseError('Failed to parse: ' + line)
|
2021-11-06 09:30:57 +08:00
|
|
|
return kconfig
|