feat(sprout): generator variants (#105)

Generators can now declare variant axes. Every generated entry is
multiplied by each combination of choices, one per axis. A choice sets
the axis name to the choice name and inserts its own values, and the
entry name is suffixed with the choice names.

Generators can also declare exclude rules, which remove generated
entries whose values match every glob pattern in a rule. This covers
unwanted variant combinations and entries like BLS rescue kernels.

Entry matching for default-entry and the bootloader interface now
accepts `*` anywhere in the pattern instead of only at the end, so
generated entry names can be matched regardless of the kernel version
in the middle of the name.

The BLS generator gains a pin-names option (default true, the existing
behavior). Two BLS generators reading the same directory previously
produced entries with identical names; disabling pin-names prefixes
them with the generator name instead. Sprout now warns when more than
one entry has the same name.

* docs(config): note that variants suffix pinned BLS entry names

Pinned BLS entry names match the BLS entry ids, but variants still
append their choice names, so those entries need a pattern like
<id>-* to be selected.
This commit is contained in:
alex authored and GitHub committed 2026-10-03 15:16:05 -07:00
1 parent 1cd9f67c48
commit 96b6c6f86a
9 files changed
+419 -26

No files matched your search

+32
View File
@@ -1,6 +1,10 @@
use crate::generators::bls::BlsConfiguration;
use crate::generators::list::ListConfiguration;
use crate::generators::matrix::MatrixConfiguration;
use crate::generators::variants::VariantChoice;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
/// Configuration for the BLS generator.
@@ -12,6 +16,9 @@ pub mod list;
/// Configuration for the matrix generator.
pub mod matrix;
/// Configuration for generator variants.
pub mod variants;
/// Declares a generator configuration.
/// Generators allow generating entries at runtime based on a set of data.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
@@ -37,4 +44,29 @@ pub struct GeneratorDeclaration {
/// List generator configuration.
/// Allows you to specify a list of values to generate an entry from.
pub list: Option<ListConfiguration>,
/// Variants to apply to every entry produced by the generator.
/// Each key is an axis, and each axis has a list of choices. Every generated entry
/// is multiplied by every combination of choices, one choice from each axis.
/// For example,
/// variants.console = [{ name = "serial" }, { name = "graphics" }]
/// variants.mode = [{ name = "normal" }, { name = "debug", values.mode-options = "loglevel=7" }]
/// would turn each generated entry into four entries:
/// console = serial, mode = normal
/// console = serial, mode = debug
/// console = graphics, mode = normal
/// console = graphics, mode = debug
/// The axis name is set to the name of the chosen choice, and then the values of the
/// chosen choice are inserted, so `$console` and `$mode-options` can be used by the entry.
/// The entry name is suffixed with the choice names, like `-graphics-debug`.
/// Axes are applied in alphabetical order. Choices are applied in the declared order.
#[serde(default)]
pub variants: BTreeMap<String, Vec<VariantChoice>>,
/// Rules that exclude generated entries, applied after variants.
/// Each rule is a map of value names to glob patterns, where `*` matches anything.
/// An entry is excluded if every value named in any one rule matches its pattern.
/// Values are matched before they are stamped. For example,
/// exclude = [{ version = "0-rescue-*" }, { console = "graphics", mode = "debug" }]
/// excludes BLS rescue entries and the combination of the graphics and debug variants.
#[serde(default)]
pub exclude: Vec<BTreeMap<String, String>>,
}
+25 -1
View File
@@ -8,13 +8,37 @@ const BLS_TEMPLATE_PATH: &str = "\\loader";
/// The configuration of the BLS generator.
/// The BLS uses the Bootloader Specification to produce
/// entries from an input template.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BlsConfiguration {
/// The entry to use for as a template.
pub entry: EntryDeclaration,
/// The path to the BLS directory.
#[serde(default = "default_bls_path")]
pub path: String,
/// Whether generated entries keep the name of the BLS entry file as-is.
/// When enabled, the generator name is not prepended, so the entry names match
/// the BLS entry ids used by tools like `bootctl set-default`. Variants still append
/// their choice names, so entries of a generator with variants never match the ids
/// exactly, and are best selected with a pattern like `<id>-*`. Two BLS generators
/// that read the same BLS directory will produce entries with the same names, so
/// generators that reuse a BLS directory should disable this.
#[serde(default = "default_pin_names", rename = "pin-names")]
pub pin_names: bool,
}
/// The default configuration, which matches an empty BLS generator section.
impl Default for BlsConfiguration {
fn default() -> Self {
Self {
entry: Default::default(),
path: default_bls_path(),
pin_names: default_pin_names(),
}
}
}
fn default_pin_names() -> bool {
true
}
fn default_bls_path() -> String {
+15
View File
@@ -0,0 +1,15 @@
use alloc::collections::BTreeMap;
use alloc::string::String;
use serde::{Deserialize, Serialize};
/// A single choice of a variant axis.
/// See [crate::generators::GeneratorDeclaration::variants] for how variants are applied.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct VariantChoice {
/// The name of the choice. This is set as the value of the axis name,
/// and is appended to the name of each entry the choice applies to.
pub name: String,
/// The values to insert into the context of each entry the choice applies to.
#[serde(default)]
pub values: BTreeMap<String, String>,
}