From b215067fb88705f6d13a288a3f00cd2c44cb80c1 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 8 Feb 2023 08:53:06 +0100 Subject: [PATCH] annotations: cleanups + always save configs with note at the top Signed-off-by: Juerg Haefliger Signed-off-by: Andrea Righi --- annotations | 20 +++--- kconfig/annotations.py | 138 +++++++++++++++++++++++++---------------- 2 files changed, 94 insertions(+), 64 deletions(-) diff --git a/annotations b/annotations index 45548fc..5caa3b2 100755 --- a/annotations +++ b/annotations @@ -53,19 +53,19 @@ def make_parser(): ga = parser.add_argument_group(title='Action').add_mutually_exclusive_group(required=False) ga.add_argument('--write', '-w', action='store', - metavar='VALUE', dest='value', - help='Set a specific config value in annotations (use \'null\' to remove)') + metavar='VALUE', dest='value', + help='Set a specific config value in annotations (use \'null\' to remove)') ga.add_argument('--export', '-e', action='store_true', - help='Convert annotations to .config format') + help='Convert annotations to .config format') ga.add_argument('--import', '-i', action='store', - metavar="FILE", dest='import_file', - help='Import a full .config for a specific arch and flavour into annotations') + metavar="FILE", dest='import_file', + help='Import a full .config for a specific arch and flavour into annotations') ga.add_argument('--update', '-u', action='store', - metavar="FILE", dest='update_file', - help='Import a partial .config into annotations (only resync configs specified in FILE)') + metavar="FILE", dest='update_file', + help='Import a partial .config into annotations (only resync configs specified in FILE)') ga.add_argument('--check', '-k', action='store', - metavar="FILE", dest='check_file', - help='Validate kernel .config with annotations') + metavar="FILE", dest='check_file', + help='Validate kernel .config with annotations') return parser _ARGPARSER = make_parser() @@ -218,7 +218,7 @@ def autodetect_annotations(args): try: with open('debian/debian.env', 'rt') as fd: args.file = fd.read().rstrip().split('=')[1] + '/config/annotations' - except: + except Exception: arg_fail('error: could not determine DEBDIR, try using: --file/-f') def main(): diff --git a/kconfig/annotations.py b/kconfig/annotations.py index dd9c05f..113ce53 100644 --- a/kconfig/annotations.py +++ b/kconfig/annotations.py @@ -50,13 +50,17 @@ class Annotation(Config): Parse body of annotations file """ def _parse_body(self, data: str): - # Skip comments - data = re.sub(r'(?m)^\s*#.*\n?', '', data) + for line in data.splitlines(): + # Replace tabs with spaces, squeeze multiple into singles and + # remove leading and trailing spaces + line = line.replace('\t', ' ') + line = re.sub(r' +', ' ', line) + line = line.strip() - # Convert multiple spaces to single space to simplifly parsing - data = re.sub(r' *', ' ', data) + # Ignore empty lines and comments + if not line or line.startswith('#'): + continue - for line in data.splitlines(): # Handle includes (recursively) m = re.match(r'^include\s+"?([^"]*)"?', line) if m: @@ -64,33 +68,38 @@ class Annotation(Config): include_fname = dirname(abspath(self.fname)) + '/' + m.group(1) include_data = self._load(include_fname) self._parse_body(include_data) - else: - # Handle policy and note lines - if re.match(r'.* (policy|note)<', line): - try: - conf = line.split(' ')[0] - if conf in self.config: - entry = self.config[conf] - else: - entry = {'policy': {}} - - match = False - m = re.match(r'.* policy<(.*?)>', line) - if m: - match = True - entry['policy'] |= literal_eval(m.group(1)) - - m = re.match(r'.* note<(.*?)>', line) - if m: - entry['oneline'] = match - match = True - entry['note'] = "'" + m.group(1).replace("'", '') + "'" - - if not match: - raise Exception('syntax error') - self.config[conf] = entry - except Exception as e: - raise Exception(str(e) + f', line = {line}') + continue + + # Handle policy and note lines + if re.match(r'.* (policy|note)<', line): + try: + conf = line.split(' ')[0] + if conf in self.config: + entry = self.config[conf] + else: + entry = {'policy': {}} + + match = False + m = re.match(r'.* policy<(.*?)>', line) + if m: + match = True + entry['policy'] |= literal_eval(m.group(1)) + + m = re.match(r'.* note<(.*?)>', line) + if m: + entry['oneline'] = match + match = True + entry['note'] = "'" + m.group(1).replace("'", '') + "'" + + if not match: + raise Exception('syntax error') + self.config[conf] = entry + except Exception as e: + raise Exception(str(e) + f', line = {line}') + continue + + # Invalid line + raise Exception(f'invalid line: {line}') """ Parse main annotations file, individual config options can be accessed via @@ -124,11 +133,11 @@ class Annotation(Config): # Parse body (handle includes recursively) self._parse_body(data) - def _remove_entry(self, config : str): + def _remove_entry(self, config: str): if self.config[config]: del self.config[config] - def remove(self, config : str, arch: str = None, flavour: str = None): + def remove(self, config: str, arch: str = None, flavour: str = None): if config not in self.config: return if arch is not None: @@ -142,11 +151,11 @@ class Annotation(Config): else: self._remove_entry(config) - def set(self, config : str, arch: str = None, flavour: str = None, - value : str = None, note : str = None): + def set(self, config: str, arch: str = None, flavour: str = None, + value: str = None, note: str = None): if value is not None: if config not in self.config: - self.config[config] = { 'policy': {} } + self.config[config] = {'policy': {}} if arch is not None: if flavour is not None: flavour = f'{arch}-{flavour}' @@ -253,6 +262,17 @@ class Annotation(Config): if not self.config[conf]['policy']: del self.config[conf] + def _sorted(self, config): + """ Sort configs alphabetically but return configs with a note first """ + w_note = [] + wo_note = [] + for c in sorted(config): + if 'note' in config[c]: + w_note.append(c) + else: + wo_note.append(c) + return w_note + wo_note + def save(self, fname: str): """ Save annotations data to the annotation file """ # Compact annotations structure @@ -275,26 +295,36 @@ class Annotation(Config): tmp_a = Annotation(fname) # Only save local differences (preserve includes) - for conf in sorted(self.config): - old_val = tmp_a.config[conf] if conf in tmp_a.config else None + marker = False + for conf in self._sorted(self.config): new_val = self.config[conf] + if 'policy' not in new_val: + continue + # If new_val is a subset of old_val, skip it - if old_val and 'policy' in old_val and 'policy' in new_val: + old_val = tmp_a.config.get(conf) + if old_val and 'policy' in old_val: if old_val['policy'] == old_val['policy'] | new_val['policy']: continue - if 'policy' in new_val: - val = dict(sorted(new_val['policy'].items())) - line = f"{conf : <47} policy<{val}>" - if 'note' in new_val: - val = new_val['note'] - if new_val.get('oneline', False): - # Single line - line += f' note<{val}>' - else: - # Separate policy and note lines, - # followed by an empty line - line += f'\n{conf : <47} note<{val}>\n' - tmp.write(line + "\n") + + # Write out the policy (and note) line(s) + val = dict(sorted(new_val['policy'].items())) + line = f"{conf : <47} policy<{val}>" + if 'note' in new_val: + val = new_val['note'] + if new_val.get('oneline', False): + # Single line + line += f' note<{val}>' + else: + # Separate policy and note lines, + # followed by an empty line + line += f'\n{conf : <47} note<{val}>\n' + elif not marker: + # Write out a marker indicating the start of annotations + # without notes + tmp.write('\n# ---- Annotations without notes ----\n\n') + marker = True + tmp.write(line + "\n") # Replace annotations with the updated version tmp.flush() @@ -316,7 +346,7 @@ class Annotation(Config): # Get config options of a specific architecture ret = {} for c in self.config: - if not 'policy' in self.config[c]: + if 'policy' not in self.config[c]: continue if flavour in self.config[c]['policy']: ret[c] = self.config[c]['policy'][flavour] -- 2.31.1