diff mbox series

[RFC,v2,1/2] bitbake-setup: add source choices

Message ID 20260618-bb-setup-source-choices-integrated-tests-v2-1-66b3b438f073@toradex.com
State New
Headers show
Series bitbake-setup: add source choices | expand

Commit Message

Ernest Van Hoecke June 18, 2026, 6:04 p.m. UTC
From: Ernest Van Hoecke <ernest.vanhoecke@toradex.com>

Allow configuration files to define source-choices instead of sources.
A source choice is selected before the BitBake configuration and then
used as the source set for checkout, status and update operations.

The positional command line grammar becomes source-first for configs
that use source-choices:

  bitbake-setup init <config> [source-choice] [bitbake-config] [fragments...]

This keeps parsing deterministic when source choice names, BitBake
configuration names and fragment choice names overlap. The selected
source choice is stored in config-upstream.json and included in the
recorded non-interactive command line options so replay remains explicit.

Source choices can carry their own expires field. Expired choices are
not offered during interactive selection or auto-selection, but an
explicit expired choice is still accepted with the same warning style as
expired configurations.

Signed-off-by: Ernest Van Hoecke <ernest.vanhoecke@toradex.com>
---
 bin/bitbake-setup                      | 71 ++++++++++++++++++++++++++++++++--
 setup-schema/bitbake-setup.schema.json | 41 ++++++++++++++++++++
 2 files changed, 109 insertions(+), 3 deletions(-)

Comments

Alexander Kanavin June 19, 2026, 10:50 a.m. UTC | #1
On Thu, 18 Jun 2026 at 20:04, Ernest Van Hoecke
<ernestvanhoecke@gmail.com> wrote:
>
> From: Ernest Van Hoecke <ernest.vanhoecke@toradex.com>
>
> Allow configuration files to define source-choices instead of sources.
> A source choice is selected before the BitBake configuration and then
> used as the source set for checkout, status and update operations.

Thanks, I think this is more or less what I had in mind. For the other
two emails I'll write separate responses, as I have some notes.

Alex
diff mbox series

Patch

diff --git a/bin/bitbake-setup b/bin/bitbake-setup
index 664bffee310d..1594d6f00dfb 100755
--- a/bin/bitbake-setup
+++ b/bin/bitbake-setup
@@ -462,8 +462,14 @@  def merge_overrides_into_sources(sources, overrides):
             layers[k] = v
     return layers
 
+def get_selected_sources(config):
+    source_choices = config["data"].get("source-choices")
+    if source_choices is not None:
+        return source_choices[config["selected-source-choice"]]["sources"]
+    return config["data"]["sources"]
+
 def update_build(config, confdir, setupdir, layerdir, d, update_bb_conf="prompt", init_vscode=False, rebase_conflicts_strategy='abort'):
-    layer_config = merge_overrides_into_sources(config["data"]["sources"], config["source-overrides"]["sources"])
+    layer_config = merge_overrides_into_sources(get_selected_sources(config), config["source-overrides"]["sources"])
     sources_fixed_revisions = checkout_layers(layer_config, confdir, layerdir, d, rebase_conflicts_strategy=rebase_conflicts_strategy)
     bitbake_config = config["bitbake-config"]
     thisdir = os.path.dirname(config["path"]) if config["type"] == 'local' else None
@@ -594,6 +600,44 @@  def choose_fragments(possibilities, parameters, non_interactive, skip_selection)
         choices[k] = options_enumerated[option_n][1]["name"]
     return choices
 
+def choose_source_choice(source_choices, source_choice_arg, non_interactive):
+    if not source_choices:
+        raise Exception("Configuration template must define at least one source choice.")
+
+    source_choice_names = sorted(source_choices.keys())
+    not_expired_choices = [k for k in source_choice_names if not has_expired(source_choices[k].get("expires", None))]
+    if source_choice_arg is not None:
+        if source_choice_arg in source_choices:
+            return source_choice_arg
+        raise Exception("Source choice {} not found; replace with one of {}".format(source_choice_arg, source_choice_names))
+
+    if len(not_expired_choices) == 1:
+        only_choice = not_expired_choices[0]
+        logger.plain("\nSelecting the only available source choice {}".format(only_choice))
+        return only_choice
+
+    if not not_expired_choices:
+        raise Exception("No unexpired source choices available.")
+
+    if non_interactive:
+        raise Exception("Unable to choose from source choices in non-interactive mode: {}".format(not_expired_choices))
+
+    descs = []
+    for c in not_expired_choices:
+        d = source_choices[c]["description"]
+        expiry_date = source_choices[c].get("expires", None)
+        if expiry_date:
+            d += f" (supported until {expiry_date})"
+        descs.append(d)
+
+    logger.plain("")
+    print_configs("Available source choices",
+                  not_expired_choices,
+                  descs)
+    choice_n = int_input([i[0] for i in list(enumerate(not_expired_choices, 1))],
+                         "\nPlease select one of the above source choices by its number: ") - 1
+    return not_expired_choices[choice_n]
+
 def obtain_config(top_dir, registry, args, source_overrides, d):
     if args.config:
         config_id = args.config[0]
@@ -632,9 +676,30 @@  def obtain_config(top_dir, registry, args, source_overrides, d):
         config_parameters = []
         upstream_config = {'type':'registry','registry':registry,'name':config_id,'data':json.load(open(get_registry_config(registry_path,config_id)))}
 
+    selected_source_choice = None
+    has_sources = 'sources' in upstream_config['data']
+    has_source_choices = 'source-choices' in upstream_config['data']
+    if has_sources == has_source_choices:
+        raise Exception("Configuration template must define exactly one of 'sources' or 'source-choices'.")
+    if has_source_choices:
+        source_choices = upstream_config['data']['source-choices']
+        source_choice_arg = config_parameters[0] if config_parameters else None
+        selected_source_choice = choose_source_choice(source_choices, source_choice_arg, args.non_interactive)
+        if source_choice_arg is not None:
+            config_parameters = config_parameters[1:]
+        expiry_date = source_choices[selected_source_choice].get("expires", None)
+        if has_expired(expiry_date):
+            logger.warning("The selected source choice {} is no longer supported after {}. Please consider changing to a supported source choice.".format(selected_source_choice, expiry_date))
+        upstream_config['selected-source-choice'] = selected_source_choice
+
     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)
-    upstream_config['non-interactive-cmdline-options'] = [config_id, upstream_config['bitbake-config']['name']] + sorted(upstream_config['bitbake-config']['oe-fragment-choices'].values())
+    upstream_config['non-interactive-cmdline-options'] = (
+        [config_id] +
+        ([selected_source_choice] if selected_source_choice is not None else []) +
+        [upstream_config['bitbake-config']['name']] +
+        sorted(upstream_config['bitbake-config']['oe-fragment-choices'].values())
+    )
     upstream_config['source-overrides'] = source_overrides
     upstream_config['skip-selection'] = args.skip_selection
     return upstream_config
@@ -952,7 +1017,7 @@  def build_status(top_dir, settings, args, d, update=False):
             bb.process.run(["git", "-C", confdir, "restore", "config-upstream.json"])
         return
 
-    layer_config = merge_overrides_into_sources(current_upstream_config["data"]["sources"], current_upstream_config["source-overrides"]["sources"])
+    layer_config = merge_overrides_into_sources(get_selected_sources(current_upstream_config), current_upstream_config["source-overrides"]["sources"])
     if are_layers_changed(layer_config, layerdir, d):
         if update:
             update_build(current_upstream_config, confdir, setupdir, layerdir,
diff --git a/setup-schema/bitbake-setup.schema.json b/setup-schema/bitbake-setup.schema.json
index 99f47f73d0a6..bf5065eb496b 100644
--- a/setup-schema/bitbake-setup.schema.json
+++ b/setup-schema/bitbake-setup.schema.json
@@ -15,6 +15,35 @@ 
         "sources": {
             "$ref": "layers.schema.json#/properties/sources"
         },
+        "source-choices": {
+            "type": "object",
+            "description": "Named choices of sources to select from",
+            "minProperties": 1,
+            "patternProperties": {
+                ".+": {
+                    "type": "object",
+                    "required": [
+                        "description",
+                        "sources"
+                    ],
+                    "properties": {
+                        "description": {
+                            "type": "string",
+                            "description": "Human-readable description of the source choice"
+                        },
+                        "sources": {
+                            "$ref": "layers.schema.json#/properties/sources"
+                        },
+                        "expires": {
+                            "type": "string",
+                            "description": "End of life date for this source choice, in ISO 8601 format (YYYY-MM-DD)"
+                        }
+                    },
+                    "additionalProperties": false
+                }
+            },
+            "additionalProperties": false
+        },
         "expires": {
             "type": "string",
             "description": "End of life date for this configuration, in ISO 8601 format (YYYY-MM-DD)"
@@ -152,5 +181,17 @@ 
             ]
         }
     },
+    "oneOf": [
+        {
+            "required": [
+                "sources"
+            ]
+        },
+        {
+            "required": [
+                "source-choices"
+            ]
+        }
+    ],
     "additionalProperties": false
 }