18c2ecf20Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#
38c2ecf20Sopenharmony_ci# Builds a .config from a kunitconfig.
48c2ecf20Sopenharmony_ci#
58c2ecf20Sopenharmony_ci# Copyright (C) 2019, Google LLC.
68c2ecf20Sopenharmony_ci# Author: Felix Guo <felixguoxiuping@gmail.com>
78c2ecf20Sopenharmony_ci# Author: Brendan Higgins <brendanhiggins@google.com>
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ciimport collections
108c2ecf20Sopenharmony_ciimport re
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ciCONFIG_IS_NOT_SET_PATTERN = r'^# CONFIG_(\w+) is not set$'
138c2ecf20Sopenharmony_ciCONFIG_PATTERN = r'^CONFIG_(\w+)=(\S+|".*")$'
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ciKconfigEntryBase = collections.namedtuple('KconfigEntryBase', ['name', 'value'])
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ciclass KconfigEntry(KconfigEntryBase):
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci	def __str__(self) -> str:
208c2ecf20Sopenharmony_ci		if self.value == 'n':
218c2ecf20Sopenharmony_ci			return r'# CONFIG_%s is not set' % (self.name)
228c2ecf20Sopenharmony_ci		else:
238c2ecf20Sopenharmony_ci			return r'CONFIG_%s=%s' % (self.name, self.value)
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ciclass KconfigParseError(Exception):
278c2ecf20Sopenharmony_ci	"""Error parsing Kconfig defconfig or .config."""
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ciclass Kconfig(object):
318c2ecf20Sopenharmony_ci	"""Represents defconfig or .config specified using the Kconfig language."""
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	def __init__(self):
348c2ecf20Sopenharmony_ci		self._entries = []
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	def entries(self):
378c2ecf20Sopenharmony_ci		return set(self._entries)
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	def add_entry(self, entry: KconfigEntry) -> None:
408c2ecf20Sopenharmony_ci		self._entries.append(entry)
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	def is_subset_of(self, other: 'Kconfig') -> bool:
438c2ecf20Sopenharmony_ci		for a in self.entries():
448c2ecf20Sopenharmony_ci			found = False
458c2ecf20Sopenharmony_ci			for b in other.entries():
468c2ecf20Sopenharmony_ci				if a.name != b.name:
478c2ecf20Sopenharmony_ci					continue
488c2ecf20Sopenharmony_ci				if a.value != b.value:
498c2ecf20Sopenharmony_ci					return False
508c2ecf20Sopenharmony_ci				found = True
518c2ecf20Sopenharmony_ci			if a.value != 'n' and found == False:
528c2ecf20Sopenharmony_ci				return False
538c2ecf20Sopenharmony_ci		return True
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	def write_to_file(self, path: str) -> None:
568c2ecf20Sopenharmony_ci		with open(path, 'w') as f:
578c2ecf20Sopenharmony_ci			for entry in self.entries():
588c2ecf20Sopenharmony_ci				f.write(str(entry) + '\n')
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	def parse_from_string(self, blob: str) -> None:
618c2ecf20Sopenharmony_ci		"""Parses a string containing KconfigEntrys and populates this Kconfig."""
628c2ecf20Sopenharmony_ci		self._entries = []
638c2ecf20Sopenharmony_ci		is_not_set_matcher = re.compile(CONFIG_IS_NOT_SET_PATTERN)
648c2ecf20Sopenharmony_ci		config_matcher = re.compile(CONFIG_PATTERN)
658c2ecf20Sopenharmony_ci		for line in blob.split('\n'):
668c2ecf20Sopenharmony_ci			line = line.strip()
678c2ecf20Sopenharmony_ci			if not line:
688c2ecf20Sopenharmony_ci				continue
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci			match = config_matcher.match(line)
718c2ecf20Sopenharmony_ci			if match:
728c2ecf20Sopenharmony_ci				entry = KconfigEntry(match.group(1), match.group(2))
738c2ecf20Sopenharmony_ci				self.add_entry(entry)
748c2ecf20Sopenharmony_ci				continue
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci			empty_match = is_not_set_matcher.match(line)
778c2ecf20Sopenharmony_ci			if empty_match:
788c2ecf20Sopenharmony_ci				entry = KconfigEntry(empty_match.group(1), 'n')
798c2ecf20Sopenharmony_ci				self.add_entry(entry)
808c2ecf20Sopenharmony_ci				continue
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci			if line[0] == '#':
838c2ecf20Sopenharmony_ci				continue
848c2ecf20Sopenharmony_ci			else:
858c2ecf20Sopenharmony_ci				raise KconfigParseError('Failed to parse: ' + line)
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	def read_from_file(self, path: str) -> None:
888c2ecf20Sopenharmony_ci		with open(path, 'r') as f:
898c2ecf20Sopenharmony_ci			self.parse_from_string(f.read())
90