diff mbox series

[RFC] bitbake-setup: preprocess "//" comments

Message ID 20251004154959.1743199-1-yoann.congal@smile.fr
State New
Headers show
Series [RFC] bitbake-setup: preprocess "//" comments | expand

Commit Message

Yoann Congal Oct. 4, 2025, 3:49 p.m. UTC
From: Yoann Congal <yoann.congal@smile.fr>

One major limitation of JSON files is that comments are not supported.
One way to support these can be to preprocess JSON files using a custom
JSONDecoder.raw_decode.

I put an example of a commented JSON config in
default-registry/configurations/poky-master_jsonc.conf.json
This is poky-master.conf.json plus some comments.

To try this:
  ./bin/bitbake-setup init default-registry/configurations/poky-master_jsonc.conf.json poky_jsonc

Signed-off-by: Yoann Congal <yoann.congal@smile.fr>
---
 bin/bitbake-setup                             | 36 ++++++--
 .../poky-master_jsonc.conf.json               | 82 +++++++++++++++++++
 2 files changed, 111 insertions(+), 7 deletions(-)
 create mode 100644 default-registry/configurations/poky-master_jsonc.conf.json
diff mbox series

Patch

diff --git a/bin/bitbake-setup b/bin/bitbake-setup
index e7b95521..62a42c81 100755
--- a/bin/bitbake-setup
+++ b/bin/bitbake-setup
@@ -18,6 +18,28 @@  import configparser
 import datetime
 import glob
 import subprocess
+import re
+
+class JSONComment(json.JSONDecoder):
+    @staticmethod
+    def remove_comments(code: str) -> str:
+        # Remove // comments while preserving those inside strings
+        # This regex matches either quoted strings or // comments
+        pattern = re.compile(r'"(?:[^"\\]|\\.)*"|//.*?(?=\n|$)', re.MULTILINE)
+
+        def replacer(match):
+            # If the match starts with a quote, it's a string - keep it
+            if match.group(0).startswith('"'):
+                return match.group(0)
+            # Otherwise it's a comment - remove it
+            return ''
+
+        return pattern.sub(replacer, code)
+    def raw_decode(self, s, idx):
+        orig_len=len(s)
+        s = JSONComment.remove_comments(s)
+        ret = super().raw_decode(s, idx)
+        return ret[0], orig_len
 
 default_registry = os.path.normpath(os.path.dirname(__file__) + "/../default-registry")
 
@@ -373,20 +395,20 @@  def obtain_config(settings, args, source_overrides, d):
             upstream_config = {'type':'local',
                                'path':os.path.abspath(config_id),
                                'name':get_config_name(config_id),
-                               'data':json.load(open(config_id))
+                               'data':json.load(open(config_id), cls=JSONComment)
                                }
         elif config_id.startswith("http://") or config_id.startswith("https://"):
             print("Reading configuration from network URI\n    {}".format(config_id))
             import urllib.request
             with urllib.request.urlopen(config_id) as f:
-              upstream_config = {'type':'network','uri':config_id,'name':get_config_name(config_id),'data':json.load(f)}
+              upstream_config = {'type':'network','uri':config_id,'name':get_config_name(config_id),'data':json.load(f, cls=JSONComment)}
         else:
             print("Looking up config {} in configuration registry".format(config_id))
             registry_path = update_registry(settings["default"]["registry"], cache_dir(args.top_dir), d)
             registry_configs = list_registry(registry_path, with_expired=True)
             if config_id not in registry_configs:
                 raise Exception("Config {} not found in configuration registry, re-run 'init' without parameters to choose from available configurations.".format(config_id))
-            upstream_config = {'type':'registry','registry':settings["default"]["registry"],'name':config_id,'data':json.load(open(get_registry_config(registry_path,config_id)))}
+            upstream_config = {'type':'registry','registry':settings["default"]["registry"],'name':config_id,'data':json.load(open(get_registry_config(registry_path,config_id)), cls=JSONComment)}
         expiry_date = upstream_config['data'].get("expires", None)
         if has_expired(expiry_date):
             print("This configuration is no longer supported after {}. Please consider changing to a supported configuration.".format(expiry_date))
@@ -395,7 +417,7 @@  def obtain_config(settings, args, source_overrides, d):
         registry_configs = list_registry(registry_path, with_expired=True)
         config_id = choose_config(registry_configs, args.non_interactive)
         config_parameters = []
-        upstream_config = {'type':'registry','registry':settings["default"]["registry"],'name':config_id,'data':json.load(open(get_registry_config(registry_path,config_id)))}
+        upstream_config = {'type':'registry','registry':settings["default"]["registry"],'name':config_id,'data':json.load(open(get_registry_config(registry_path,config_id)), cls=JSONComment)}
 
     upstream_config['bitbake-config'] = choose_bitbake_config(upstream_config['data']['bitbake-setup']['configurations'], config_parameters, args.non_interactive)
     upstream_config['bitbake-config']['oe-fragment-choices'] = choose_fragments(upstream_config['bitbake-config'].get('oe-fragments-one-of',{}), config_parameters[1:], args.non_interactive, args.skip_selection)
@@ -411,7 +433,7 @@  def init_config(settings, args, d):
         progress = event.progress if event.progress > 0 else 0
         print("{}% {}                ".format(progress, rate), file=stdout, end='\r')
 
-    source_overrides = json.load(open(args.source_overrides)) if args.source_overrides else {'sources':{}}
+    source_overrides = json.load(open(args.source_overrides), cls=JSONComment) if args.source_overrides else {'sources':{}}
     upstream_config = obtain_config(settings, args, source_overrides, d)
     print("\nRun 'bitbake-setup init --non-interactive {}' to select this configuration non-interactively.\n".format(" ".join(upstream_config['non-interactive-cmdline-options'])))
 
@@ -492,7 +514,7 @@  def build_status(settings, args, d, update=False):
     confdir = os.path.join(builddir, "config")
     layerdir = os.path.join(builddir, "layers")
 
-    current_upstream_config = json.load(open(os.path.join(confdir, "config-upstream.json")))
+    current_upstream_config = json.load(open(os.path.join(confdir, "config-upstream.json")), cls=JSONComment)
 
     args.config = current_upstream_config['non-interactive-cmdline-options']
     args.non_interactive = True
@@ -560,7 +582,7 @@  def list_registry(registry_path, with_expired):
         for f in files:
             if f.endswith('.conf.json'):
                 config_name = get_config_name(f)
-                config_data = json.load(open(os.path.join(root, f)))
+                config_data = json.load(open(os.path.join(root, f)), cls=JSONComment)
                 config_desc = config_data["description"]
                 expiry_date = config_data.get("expires", None)
                 if expiry_date:
diff --git a/default-registry/configurations/poky-master_jsonc.conf.json b/default-registry/configurations/poky-master_jsonc.conf.json
new file mode 100644
index 00000000..392200d3
--- /dev/null
+++ b/default-registry/configurations/poky-master_jsonc.conf.json
@@ -0,0 +1,82 @@ 
+{
+    "description": "config to demonstrate inline comments: This is a \"//test",  // Inline comment (even "this")
+    "sources": {
+        "bitbake": {
+            "git-remote": {
+                "remotes": {
+                    "origin": {
+                        "uri": "git://git.openembedded.org/bitbake;protocol=https" // << this is not cut 
+                    }
+                },
+                "branch": "master",
+                "rev": "master"
+            },
+            "path": "bitbake"
+        },
+        "openembedded-core": {
+            "git-remote": {
+                "remotes": {
+                    "origin": {
+                        "uri": "git://git.openembedded.org/openembedded-core;protocol=https"
+                    }
+                },
+                "branch": "master",
+                "rev": "master"
+            },
+            "path": "openembedded-core"
+        },
+        "meta-yocto": {
+            "git-remote": {
+                "remotes": {
+                    "origin": {
+                        "uri": "git://git.yoctoproject.org/meta-yocto;protocol=https"
+                    }
+                },
+                "branch": "master",
+                "rev": "master"
+            },
+            "path": "meta-yocto"
+        },
+        "yocto-docs": {
+            "git-remote": {
+                "remotes": {
+                    "origin": {
+                        "uri": "git://git.yoctoproject.org/yocto-docs;protocol=https"
+                    }
+                },
+                "branch": "master",
+                "rev": "master"
+            },
+            "path": "yocto-docs"
+        }
+    },
+    "bitbake-setup": {
+        "configurations": [
+        {
+            "bb-layers": ["openembedded-core/meta","meta-yocto/meta-yocto-bsp","meta-yocto/meta-poky"],
+            "oe-fragments-one-of": {
+                "machine": {
+                    "description": "Target machines",
+                    "options" : ["machine/qemux86-64", "machine/qemuarm64", "machine/qemuriscv64", "machine/genericarm64", "machine/genericx86-64"]
+                },
+                "distro": {
+                    "description": "Distribution configuration variants",
+                    "options" : ["distro/poky", "distro/poky-altcfg", "distro/poky-tiny"]
+                }
+            },
+            "configurations": [
+            {
+                "name": "poky_jsonc",
+                "description": "Poky - The Yocto Project testing distribution"
+            },
+            {
+                "name": "poky-with-sstate",
+                "description": "Poky - The Yocto Project testing distribution with internet sstate acceleration. Use with caution as it requires a completely robust local network with sufficient bandwidth.",
+                "oe-fragments": ["core/yocto/sstate-mirror-cdn"]
+            }
+            ]
+        }
+        ]
+    },
+    "version": "1.0"
+}