1# SPDX-License-Identifier: GPL-2.0 2# 3# Builds a .config from a kunitconfig. 4# 5# Copyright (C) 2019, Google LLC. 6# Author: Felix Guo <felixguoxiuping@gmail.com> 7# Author: Brendan Higgins <brendanhiggins@google.com> 8 9from dataclasses import dataclass 10import re 11from typing import Any, Dict, Iterable, List, Tuple 12 13CONFIG_IS_NOT_SET_PATTERN = r'^# CONFIG_(\w+) is not set$' 14CONFIG_PATTERN = r'^CONFIG_(\w+)=(\S+|".*")$' 15 16@dataclass(frozen=True) 17class KconfigEntry: 18 name: str 19 value: str 20 21 def __str__(self) -> str: 22 if self.value == 'n': 23 return f'# CONFIG_{self.name} is not set' 24 return f'CONFIG_{self.name}={self.value}' 25 26 27class KconfigParseError(Exception): 28 """Error parsing Kconfig defconfig or .config.""" 29 30 31class Kconfig: 32 """Represents defconfig or .config specified using the Kconfig language.""" 33 34 def __init__(self) -> None: 35 self._entries = {} # type: Dict[str, str] 36 37 def __eq__(self, other: Any) -> bool: 38 if not isinstance(other, self.__class__): 39 return False 40 return self._entries == other._entries 41 42 def __repr__(self) -> str: 43 return ','.join(str(e) for e in self.as_entries()) 44 45 def as_entries(self) -> Iterable[KconfigEntry]: 46 for name, value in self._entries.items(): 47 yield KconfigEntry(name, value) 48 49 def add_entry(self, name: str, value: str) -> None: 50 self._entries[name] = value 51 52 def is_subset_of(self, other: 'Kconfig') -> bool: 53 for name, value in self._entries.items(): 54 b = other._entries.get(name) 55 if b is None: 56 if value == 'n': 57 continue 58 return False 59 if value != b: 60 return False 61 return True 62 63 def conflicting_options(self, other: 'Kconfig') -> List[Tuple[KconfigEntry, KconfigEntry]]: 64 diff = [] # type: List[Tuple[KconfigEntry, KconfigEntry]] 65 for name, value in self._entries.items(): 66 b = other._entries.get(name) 67 if b and value != b: 68 pair = (KconfigEntry(name, value), KconfigEntry(name, b)) 69 diff.append(pair) 70 return diff 71 72 def merge_in_entries(self, other: 'Kconfig') -> None: 73 for name, value in other._entries.items(): 74 self._entries[name] = value 75 76 def write_to_file(self, path: str) -> None: 77 with open(path, 'a+') as f: 78 for e in self.as_entries(): 79 f.write(str(e) + '\n') 80 81def parse_file(path: str) -> Kconfig: 82 with open(path, 'r') as f: 83 return parse_from_string(f.read()) 84 85def parse_from_string(blob: str) -> Kconfig: 86 """Parses a string containing Kconfig entries.""" 87 kconfig = Kconfig() 88 is_not_set_matcher = re.compile(CONFIG_IS_NOT_SET_PATTERN) 89 config_matcher = re.compile(CONFIG_PATTERN) 90 for line in blob.split('\n'): 91 line = line.strip() 92 if not line: 93 continue 94 95 match = config_matcher.match(line) 96 if match: 97 kconfig.add_entry(match.group(1), match.group(2)) 98 continue 99 100 empty_match = is_not_set_matcher.match(line) 101 if empty_match: 102 kconfig.add_entry(empty_match.group(1), 'n') 103 continue 104 105 if line[0] == '#': 106 continue 107 raise KconfigParseError('Failed to parse: ' + line) 108 return kconfig 109