annotations: introduce --no-include
authorAndrea Righi <andrea.righi@canonical.com>
Mon, 11 Dec 2023 08:49:35 +0000 (09:49 +0100)
committerAndrea Righi <andrea.righi@canonical.com>
Mon, 11 Dec 2023 10:23:38 +0000 (11:23 +0100)
Add an option to skip processing included annotations files. This option
can be useful to convert files from an old format to a newer one, by
dumping the contant of the local leaf-node annotations file, instead of
including the content of external files.

Signed-off-by: Andrea Righi <andrea.righi@canonical.com>
kconfig/annotations.py
kconfig/run.py

index e3edfb57940a7ec38b3f614c2e77002a8db04cf3..44c6f66da0674b4d500bd2772c34a4031388292c 100644 (file)
@@ -16,12 +16,13 @@ from kconfig.version import ANNOTATIONS_FORMAT_VERSION
 
 
 class Config:
-    def __init__(self, fname):
+    def __init__(self, fname, do_include=True):
         """
         Basic configuration file object
         """
         self.fname = fname
         self.config = {}
+        self.do_include = do_include
 
         raw_data = self._load(fname)
         self._parse(raw_data)
@@ -91,9 +92,10 @@ class Annotation(Config):
             if m:
                 if parent:
                     self.include.append(m.group(1))
-                include_fname = dirname(abspath(self.fname)) + "/" + m.group(1)
-                include_data = self._load(include_fname)
-                self._parse_body(include_data, parent=False)
+                if self.do_include:
+                    include_fname = dirname(abspath(self.fname)) + "/" + m.group(1)
+                    include_data = self._load(include_fname)
+                    self._parse_body(include_data, parent=False)
                 continue
 
             # Handle policy and note lines
@@ -170,59 +172,60 @@ class Annotation(Config):
         if not self.flavour:
             raise SyntaxError(f"FLAVOUR not defined in annotations")
 
-        # Parse body (handle includes recursively)
+        # Parse body
         self._parse_body(data)
 
         # Sanity check: Verify that all FLAVOUR_DEP flavors are valid
-        for src, tgt in self.flavour_dep.items():
-            if src not in self.flavour:
-                raise SyntaxError(f"Invalid source flavour in FLAVOUR_DEP: {src}")
-            if tgt not in self.include_flavour:
-                raise SyntaxError(f"Invalid target flavour in FLAVOUR_DEP: {tgt}")
+        if self.do_include:
+            for src, tgt in self.flavour_dep.items():
+                if src not in self.flavour:
+                    raise SyntaxError(f"Invalid source flavour in FLAVOUR_DEP: {src}")
+                if tgt not in self.include_flavour:
+                    raise SyntaxError(f"Invalid target flavour in FLAVOUR_DEP: {tgt}")
 
     def _json_parse(self, data, is_included=False):
         data = json.loads(data)
 
         # Check if version is supported
-        version = data['attributes']['_version']
+        version = data["attributes"]["_version"]
         if version > ANNOTATIONS_FORMAT_VERSION:
-                raise SyntaxError(f"annotations format version {version} not supported")
+            raise SyntaxError(f"annotations format version {version} not supported")
 
         # Check for top-level annotations vs imported annotations
         if not is_included:
-            self.config = data['config']
-            self.arch = data['attributes']['arch']
-            self.flavour = data['attributes']['flavour']
-            self.flavour_dep = data['attributes']['flavour_dep']
-            self.include = data['attributes']['include']
+            self.config = data["config"]
+            self.arch = data["attributes"]["arch"]
+            self.flavour = data["attributes"]["flavour"]
+            self.flavour_dep = data["attributes"]["flavour_dep"]
+            self.include = data["attributes"]["include"]
             self.include_flavour = []
         else:
             # We are procesing an imported annotations, so merge all the
             # configs and attributes.
             try:
-                self.config |= data['config']
+                self.config |= data["config"]
             except TypeError:
-                self.config = {
-                    **self.config,
-                    **data['config']
-                }
-            self.arch = list(set(self.arch) | set(data['attributes']['arch']))
-            self.flavour = list(set(self.flavour) | set(data['attributes']['flavour']))
-            self.include_flavour = list(set(self.include_flavour) | set(data['attributes']['flavour']))
-            self.flavour_dep = list(set(self.flavour_dep) | set(data['attributes']['flavour_dep']))
+                self.config = {**self.config, **data["config"]}
+            self.arch = list(set(self.arch) | set(data["attributes"]["arch"]))
+            self.flavour = list(set(self.flavour) | set(data["attributes"]["flavour"]))
+            self.include_flavour = list(
+                set(self.include_flavour) | set(data["attributes"]["flavour"])
+            )
+            self.flavour_dep = self.flavour_dep | data["attributes"]["flavour_dep"]
 
         # Handle recursive inclusions
-        for f in data['attributes']['include']:
-            include_fname = dirname(abspath(self.fname)) + "/" + f
-            data = self._load(include_fname)
-            self._json_parse(data, is_included=True)
+        if self.do_include:
+            for f in data["attributes"]["include"]:
+                include_fname = dirname(abspath(self.fname)) + "/" + f
+                data = self._load(include_fname)
+                self._json_parse(data, is_included=True)
 
     def _parse(self, data: str):
         # Try to parse the legacy format first, otherwise use the new JSON
         # format.
         try:
             self._legacy_parse(data)
-        except SyntaxError:
+        except SyntaxError as e:
             self._json_parse(data, is_included=False)
 
     def _remove_entry(self, config: str):
@@ -295,12 +298,16 @@ class Annotation(Config):
                 if "policy" in self.config[conf]:
                     # Add a TODO if a config with a note is changing and print
                     # a warning
-                    old_val = self.search_config(config=conf, arch=arch, flavour=flavour_arg)
+                    old_val = self.search_config(
+                        config=conf, arch=arch, flavour=flavour_arg
+                    )
                     if old_val:
                         old_val = old_val[conf]
                     if val != old_val and "note" in self.config[conf]:
-                        self.config[conf]['note'] = "TODO: update note"
-                        print(f"WARNING: {conf} changed from {old_val} to {val}, updating note")
+                        self.config[conf]["note"] = "TODO: update note"
+                        print(
+                            f"WARNING: {conf} changed from {old_val} to {val}, updating note"
+                        )
                     self.config[conf]["policy"][flavour] = val
                 else:
                     self.config[conf]["policy"] = {flavour: val}
index 992dc56b55adff8b74e8ff2e8a09e5db47e71335..17760c532ceb8daf8419de31530b09b923b81cc9 100644 (file)
@@ -80,6 +80,11 @@ def make_parser():
         action="store_true",
         help="Jump to a config definition in the kernel source code",
     )
+    parser.add_argument(
+        "--no-include",
+        action="store_true",
+        help="Do not process included annotations (stop at the main file)",
+    )
 
     ga = parser.add_argument_group(title="Action").add_mutually_exclusive_group(
         required=False
@@ -127,29 +132,34 @@ def make_parser():
 
 _ARGPARSER = make_parser()
 
+
 def export_result(data):
     # Dump metadata / attributes first
     out = '{\n  "attributes": {\n'
-    for key, value in sorted(data['attributes'].items()):
+    for key, value in sorted(data["attributes"].items()):
         out += f'     "{key}": {json.dumps(value)},\n'
-    out = out.rstrip(',\n')
-    out += '\n  },'
+    out = out.rstrip(",\n")
+    out += "\n  },"
     print(out)
 
-    configs_with_note = {key: value for key, value in data['config'].items() if 'note' in value}
-    configs_without_note = {key: value for key, value in data['config'].items() if 'note' not in value}
+    configs_with_note = {
+        key: value for key, value in data["config"].items() if "note" in value
+    }
+    configs_without_note = {
+        key: value for key, value in data["config"].items() if "note" not in value
+    }
 
     # Dump configs, sorted alphabetically, showing items with a note first
     out = '  "config": {\n'
     for key in sorted(configs_with_note) + sorted(configs_without_note):
-        policy = data['config'][key]['policy']
-        if 'note' in data['config'][key]:
-            note = data['config'][key]['note']
+        policy = data["config"][key]["policy"]
+        if "note" in data["config"][key]:
+            note = data["config"][key]["note"]
             out += f'    "{key}": {{"policy": {json.dumps(policy)}, "note": {json.dumps(note)}}},\n'
         else:
             out += f'    "{key}": {{"policy": {json.dumps(policy)}}},\n'
-    out = out.rstrip(',\n')
-    out += '\n  }\n}'
+    out = out.rstrip(",\n")
+    out += "\n  }\n}"
     print(out)
 
 
@@ -162,7 +172,7 @@ def print_result(config, data):
 def do_query(args):
     if args.arch is None and args.flavour is not None:
         arg_fail(_ARGPARSER, "error: --flavour requires --arch")
-    a = Annotation(args.file)
+    a = Annotation(args.file, do_include=(not args.no_include))
     res = a.search_config(config=args.config, arch=args.arch, flavour=args.flavour)
     # If no arguments are specified dump the whole annotations structure
     if args.config is None and args.arch is None and args.flavour is None: