annotations: cleanups + always save configs with note at the top
authorAndrea Righi <andrea.righi@canonical.com>
Wed, 8 Feb 2023 07:53:06 +0000 (08:53 +0100)
committerAndrea Righi <andrea.righi@canonical.com>
Wed, 8 Feb 2023 07:53:06 +0000 (08:53 +0100)
Signed-off-by: Juerg Haefliger <juerg.haefliger@canonical.com>
Signed-off-by: Andrea Righi <andrea.righi@canonical.com>
annotations
kconfig/annotations.py

index 45548fc59dce3a9f3a480d010f896445de74729c..5caa3b2aa70fcab125ed6d63c7b6b5da6a5944c1 100755 (executable)
@@ -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():
index dd9c05fbb8e4aa66267d0a2739ba96f61b20649f..113ce53eb4e08f6697e06d44dd69361f31cc26dd 100644 (file)
@@ -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]